diff --git a/.gitignore b/.gitignore index 15c253c2..890f8b23 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ /*.tds /*.td2 /*.map -/Makefile.bor /Makefile.mgw /Makefile.vc /Makefile.lcc @@ -39,6 +38,7 @@ /puttyapp /ptermapp /osxlaunch +/uppity /unix/PuTTY.app /unix/Pterm.app /fuzzterm @@ -128,7 +128,6 @@ /windows/*.td2 /windows/*.map /windows/Makefile.clangcl -/windows/Makefile.bor /windows/Makefile.mgw /windows/Makefile.vc /windows/Makefile.lcc diff --git a/Buildscr b/Buildscr index 7ca4eb7f..4656dde4 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 16214 # update this at every release +set Epoch 16351 # 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 @@ -63,10 +63,10 @@ set Docmakever VERSION="$(Puttytextver)" # make sure it's under 40 characters, which is a hard limit in the SSH # protocol spec (and enforced by a compile-time assertion in # version.c). -ifneq "$(RELEASE)" "" set Sshver PuTTY-Release-$(RELEASE) -ifneq "$(PRERELEASE)" "" set Sshver PuTTY-Prerelease-$(PRERELEASE):$(Ndate).$(vcsid) -ifneq "$(SNAPSHOT)" "" set Sshver PuTTY-Snapshot-$(Date).$(vcsid) -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Sshver PuTTY-Custom-$(Date).$(vcsid) +ifneq "$(RELEASE)" "" set Sshver -Release-$(RELEASE) +ifneq "$(PRERELEASE)" "" set Sshver -Prerelease-$(PRERELEASE):$(Ndate).$(vcsid) +ifneq "$(SNAPSHOT)" "" set Sshver -Snapshot-$(Date).$(vcsid) +ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Sshver -Custom-$(Date).$(vcsid) # Set up the filename suffix for the Unix source archive. ifneq "$(RELEASE)" "" set Uxarcsuffix -$(RELEASE) @@ -88,6 +88,8 @@ ifneq "$(SNAPSHOT)" "" set Isuffix $(Date)-installer ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Isuffix custom-$(Date)-installer set Ifilename32 putty-$(Isuffix) set Ifilename64 putty-64bit-$(Isuffix) +set Ifilenamea32 putty-arm32-$(Isuffix) +set Ifilenamea64 putty-arm64-$(Isuffix) # Set up the version string for the Windows installer. ifneq "$(RELEASE)" "" set Iversion $(RELEASE) @@ -139,8 +141,7 @@ ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS) in putty do ./mksrcarc.sh in putty do ./mkunxarc.sh '$(Autoconfver)' '$(Uxarcsuffix)' $(Docmakever) in putty do perl mkfiles.pl -in putty/doc do make $(Docmakever) putty.hlp -in putty/doc do make $(Docmakever) chm +in putty/doc do make $(Docmakever) putty.hlp putty.chm -j$(nproc) # Munge the installer script locally so that it reports the version # we're really building. @@ -153,59 +154,69 @@ in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(VersionInfoVersion=) in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE # Some gratuitous theming for the MSI installer UI. -in putty/icons do make +in putty/icons do make -j$(nproc) in putty do convert -size 164x312 'gradient:blue-white' -distort SRT -90 -swirl 180 \( -size 329x312 canvas:white \) +append \( icons/putty-48.png -geometry +28+24 \) -composite \( icons/pscp-48.png -geometry +88+96 \) -composite \( icons/puttygen-48.png -geometry +28+168 \) -composite \( icons/pageant-48.png -geometry +88+240 \) -composite windows/msidialog.bmp in putty do convert -size 493x58 canvas:white \( icons/putty-48.png -geometry +440+5 \) -composite windows/msibanner.bmp -delegate windows - # Build the original binaries. - in putty/windows with visualstudio do/win mkdir buildold && nmake -f Makefile.vc BUILDDIR=buildold\ $(Makeargs) all cleantestprogs - - # Build the VS2015 binaries. 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 visualstudio2015_32bit do/win mkdir build32 && nmake -f Makefile.vc BUILDDIR=build32\ SUBSYSVER=,5.01 $(Makeargs) all cleantestprogs - in putty/windows with visualstudio2015_64bit do/win mkdir build64 && nmake -f Makefile.vc BUILDDIR=build64\ $(Makeargs) all cleantestprogs - - # Code-sign the binaries, if the local bob config provides a script - # to do so. We assume here that the script accepts an -i option to - # provide a 'more info' URL, and an optional -n option to provide a - # program name, and that it can take multiple .exe filename - # arguments and sign them all in place. - ifneq "$(winsigncode)" "" in putty/windows do $(winsigncode) -i http://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe - - # Ignore exit code from hhc, in favour of seeing whether the .chm - # file was created. (Yuck; but hhc appears to return non-zero - # exit codes on whim.) - in putty/doc with htmlhelp do/win hhc putty.hhp & type putty.chm >nul - - # Build a WiX MSI installer, for each of build32 and build64. - in putty/windows with wix do/win candle -arch x86 -dWin64=no -dBuilddir=build32\ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi - in putty/windows with wix do/win candle -arch x64 -dWin64=yes -dBuilddir=build64\ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi - - # Build the old Inno Setup installer, for 32-bit only. - in putty/windows with innosetup do/win iscc putty.iss - - # Sign the installers. - ifneq "$(winsigncode)" "" in putty/windows do $(winsigncode) -i http://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi Output/installer.exe - - # Finished Windows builds. - return putty/windows/buildold/*.exe - return putty/windows/buildold/*.map - return putty/windows/build32/*.exe - return putty/windows/build32/*.map - return putty/windows/build64/*.exe - return putty/windows/build64/*.map - return putty/doc/putty.chm - return putty/windows/installer32.msi - return putty/windows/installer64.msi - return putty/windows/Output/installer.exe -enddelegate +# Build the standard binaries, in both 32- and 64-bit flavours. +# +# 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 mkdir build32 && Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) +in putty/windows with clangcl64 do mkdir build64 && Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all -j$(nproc) + +# Build experimental Arm Windows binaries. +in putty/windows with clangcl_a32 do mkdir abuild32 && Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc) +in putty/windows with clangcl_a64 do mkdir abuild64 && Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ $(Makeargs) all -j$(nproc) + +# Build the 'old' binaries, which should still run on all 32-bit +# versions of Windows back to Win95 (but not Win32s). These link +# against Visual Studio 2003 libraries (the more modern versions +# assume excessively modern Win32 API calls to be available), specify +# a subsystem version of 4.0, and compile with /arch:IA32 to prevent +# the use of modern CPU features like MMX which older machines also +# might not have. +in putty/windows with clangcl32_2003 do mkdir buildold && Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj XFLAGS=/arch:IA32 all -j$(nproc) + +# Remove Windows binaries for the test programs we don't want to ship, +# like testbn.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 +in putty/windows do make -f Makefile.clangcl BUILDDIR=buildold/ cleantestprogs + +# 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 +# script accepts an -i option to provide a 'more info' URL, an +# optional -n option to provide a program name, and an -N option to +# 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 + +# Build a WiX MSI installer, for each of build32 and build64. +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 + +# Bodge the platform fields for the Windows on Arm installers, since +# WiX 3 doesn't understand Arm platform names itself. +in putty/windows do ./msiplatform.py installera32.msi Arm +in putty/windows do ./msiplatform.py installera64.msi Arm64 + +# 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 + in putty/doc do make mostlyclean -in putty/doc do make $(Docmakever) +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 ../../doc/putty.hlp ../../doc/putty.cnt in putty/windows/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm ../../doc/putty.hlp ../../doc/putty.cnt in putty/windows/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm ../../doc/putty.hlp ../../doc/putty.cnt +in putty/windows/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm ../../doc/putty.hlp ../../doc/putty.cnt +in putty/windows/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm ../../doc/putty.hlp ../../doc/putty.cnt in putty/doc do zip puttydoc.zip *.html # Deliver the actual PuTTY release directory into a subdir `putty'. @@ -217,7 +228,12 @@ deliver putty/windows/build64/*.exe putty/w64/$@ deliver putty/windows/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/Output/installer.exe putty/w32/$(Ifilename32).exe +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/doc/puttydoc.zip putty/$@ deliver putty/doc/putty.chm putty/$@ deliver putty/doc/putty.hlp putty/$@ @@ -231,6 +247,8 @@ deliver putty/*.tar.gz putty/$@ 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 sign.sh, so that whoever has just built PuTTY (the # snapshot scripts or me, depending) can conveniently sign it with @@ -254,4 +272,4 @@ in-dest putty do echo "AddType application/octet-stream .hlp" >> .htaccess in-dest putty do echo "AddType application/octet-stream .cnt" >> .htaccess in-dest putty do set -- putty*.tar.gz; for k in '' .gpg; do echo RedirectMatch temp '(.*/)'putty.tar.gz$$k\$$ '$$1'"$$1$$k" >> .htaccess; done # And one in each binary directory, providing links for the installers. -in-dest putty do for params in "w32 putty-installer" "w64 putty-64bit-installer"; do (set -- $$params; subdir=$$1; installername=$$2; cd $$subdir && for ext in msi exe; do set -- putty*installer.$$ext; if test -f $$1; then for k in '' .gpg; do echo RedirectMatch temp '(.*/)'$${installername}.$$ext$$k\$$ '$$1'"$$1$$k" >> .htaccess; done; fi; done); done +in-dest putty do for params in "w32 putty-installer" "w64 putty-64bit-installer" "wa32 putty-arm32-installer" "wa64 putty-arm64-installer"; do (set -- $$params; subdir=$$1; installername=$$2; cd $$subdir && for ext in msi exe; do set -- putty*installer.$$ext; if test -f $$1; then for k in '' .gpg; do echo RedirectMatch temp '(.*/)'$${installername}.$$ext$$k\$$ '$$1'"$$1$$k" >> .htaccess; done; fi; done); done diff --git a/CHECKLST.txt b/CHECKLST.txt index 0499d780..21f38656 100644 --- a/CHECKLST.txt +++ b/CHECKLST.txt @@ -24,25 +24,28 @@ pre-releases on the website: add a news announcement in components/news. (Previous naming convention has been to name it in the form 'X.YZ-pre.mi'.) -Preparing to make a release ---------------------------- +Things to do during the branch-stabilisation period: -Now that PuTTY is in git, a lot of the release preparation can be done -in advance, in local checkouts, and not pushed until the actual -process of _releasing_ it. + - Go through the source (including the documentation), and the + website, and review anything tagged with a comment containing the + word XXX-REVIEW-BEFORE-RELEASE. (Any such comments should state + clearly what needs to be done.) -To begin with, before dropping the tag, make sure everything is ready -for it: + - Do some testing of the Windows version with Minefield (you can + build a Minefield version using 'bob . XFLAGS=-DMINEFIELD'), and of + the Unix version with valgrind. In particular, any headline + features for the release should get a workout with memory checking + enabled! - - First of all, go through the source (including the documentation), - and the website, and review anything tagged with a comment - containing the word XXX-REVIEW-BEFORE-RELEASE. - (Any such comments should state clearly what needs to be done.) +Making a release candidate build +-------------------------------- - - Also, do some testing of the Windows version with Minefield, and - of the Unix version with valgrind or efence or both. In - particular, any headline features for the release should get a - workout with memory checking enabled! + - Make a directory to hold all the release paraphernalia. I usually + call it ~/src/putty/X.YZ (where X.YZ will stand throughout for the + version number). In that directory, make a git clone of the PuTTY + repository, where you can make release-related commits and tags + tentatively, and keep them out of the way of any 'git push' you + might still be doing in other checkouts. - Double-check that we have removed anything tagged with a comment containing the words XXX-REMOVE-BEFORE-RELEASE or @@ -50,9 +53,9 @@ for it: hits in this file itself.) - Now update the version numbers and the transcripts in the docs, by - checking out the release branch and running + checking out the release branch in the release-specific checkout + and running - make distclean ./release.pl --version=X.YZ --setver Then check that the resulting automated git commit has updated the @@ -72,6 +75,42 @@ for it: - If the release is on a branch (which I expect it generally will be), merge that branch to master. + - Make a release-candidate build from the release tag, and put the + build.out and build.log dfiles somewhere safe. Normally I store + these in an adjacent directory, so I'll run a command like + bob -o ../X.YZ/build-X.YZ-rcN.out -l ../X.YZ/build-X.YZ-rcN.log -c X.YZ . RELEASE=X.YZ + This should generate a basically valid release directory as + `build-X.YZ-rcN.out/putty', and provide link maps and sign.sh + alongside that. + + - Double-check in build-X.YZ-rcN.log that the release was built from + the right git commit. + + - Make a preliminary gpg signature, but don't run the full release- + signing procedure. (We use the presence of a full set of GPG + signatures to distinguish _abandoned_ release candidates from the + one that ended up being the release.) In the 'build.X.YZ-rcN.out' + directory, run + sh sign.sh -r -p putty + and you should only have to enter the release key passphrase once, + which will generate a clearsigned file called + sha512sums-preliminary.gpg _outside_ the 'putty' subdirectory. + + - For my own safety, make the release candidate build read-only. + chmod -R a-w build-X.YZ-rcN.out build-X.YZ-rcN.log + + - Now do some checking of the release binaries, and pass them to the + rest of the team to do some as well. Do at least these things: + * 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 + * test the Windows installer + * test the Unix source tarball. + +Preparing to make the release +----------------------------- + - Write a release announcement (basically a summary of the changes since the last release). Squirrel it away in thyestes:src/putty-local/announce- in case it's needed again @@ -96,33 +135,16 @@ for it: branch (so that the wishlist mechanism can't automatically mark them as fixed in the new release), add appropriate Fixed-in headers for those. - * Add an entry to the @releases array in control/bugs2html. - - - Make a release-candidate build from the release tag, and put the - build.out and build.log dfiles somewhere safe. Normally I store - these in an adjacent directory, so I'll run a command like - bob -o ../X.YZ/build-X.YZ-rcN.out -l ../X.YZ/build-X.YZ-rcN.log -c X.YZ . RELEASE=X.YZ - This should generate a basically valid release directory as - `build-X.YZ-rcN.out/putty', and provide link maps and sign.sh - alongside that. - - - Double-check in build-X.YZ-rcN.log that the release was built from - the right git commit. - - Do a bit of checking of the release binaries: - * 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 - * test the Windows installer - * test the Unix source tarball. - - - Sign the release: in the `build-X.YZ-rcN.out' directory, type + - Sign the release in full. In the `build-X.YZ-rcN.out' directory, + re-verify that the preliminary signed checksums file has a correct + signature on it and also matches the files you're about to sign for real: + gpg -d sha512sums-preliminary.gpg | (cd putty; sha512sum -c) + If the combined output of that pipeline reports both a good + signature (from the release key) and a successful verification of + all the sha512sums, then all is well, so now run sh sign.sh -r putty - and enter the passphrases a lot of times. - - - For my own safety, make the release candidate build read-only. - chmod -R a-w build-X.YZ-rcN.out build-X.YZ-rcN.log + and enter the release key passphrase a lot of times. The actual release procedure ---------------------------- diff --git a/LATEST.VER b/LATEST.VER index db1ed30c..ac37bbea 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.68 +0.70 diff --git a/LICENCE b/LICENCE index 7c49ceb3..fcd98752 100644 --- a/LICENCE +++ b/LICENCE @@ -1,9 +1,11 @@ -PuTTY is copyright 1997-2017 Simon Tatham. +PuTTY is copyright 1997-2018 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus -Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A. +Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian +Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav +Kuzmich, Nico Williams, Viktor Dukhovni, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/README b/README index ef931dd1..de6eb9b0 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -This is the README for the source archive of PuTTY, a free Win32 +This is the README for the source archive of PuTTY, a free Windows and Unix Telnet and SSH client. If you want to rebuild PuTTY from source, we provide a variety of @@ -34,10 +34,6 @@ For building on Windows: MSVC/putty/putty.dsp builds PuTTY itself, MSVC/plink/plink.dsp builds Plink, and so on. - - windows/Makefile.bor is for the Borland C compiler. Type `make -f - Makefile.bor' while in the `windows' subdirectory to build all - the PuTTY binaries. - - windows/Makefile.mgw is for MinGW / Cygwin installations. Type `make -f Makefile.mgw' while in the `windows' subdirectory to build all the PuTTY binaries. @@ -127,11 +123,11 @@ Documentation (in various formats including Windows Help and Unix `man' pages) is built from the Halibut (`.but') files in the `doc' subdirectory using `doc/Makefile'. If you aren't using one of our source snapshots, you'll need to do this yourself. Halibut can be -found at . +found at . The PuTTY home web site is - http://www.chiark.greenend.org.uk/~sgtatham/putty/ + https://www.chiark.greenend.org.uk/~sgtatham/putty/ If you want to send bug reports or feature requests, please read the Feedback section of the web site before doing so. Sending one-line diff --git a/Recipe b/Recipe index e39faa1b..13d9d8f1 100644 --- a/Recipe +++ b/Recipe @@ -16,7 +16,6 @@ !makefile vc windows/Makefile.vc !makefile vcproj windows/MSVC !makefile cygwin windows/Makefile.mgw -!makefile borland windows/Makefile.bor !makefile lcc windows/Makefile.lcc !makefile gtk unix/Makefile.gtk !makefile unix unix/Makefile.ux @@ -209,9 +208,9 @@ endif if HAVE_QUARTZ noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch - rm -rf $@ && gtk-mac-bundler $< + 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 $@ && gtk-mac-bundler $< + rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< endif !end @@ -230,51 +229,73 @@ CFLAGS += -DWINVER=0x0500 -D_WIN32_WINDOWS=0x0410 -D_WIN32_WINNT=0x0500 # 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 wcwidth ldiscucs logging tree234 minibidi - + config dialog conf + + config dialog CONF # GUI front end and terminal emulator (putty, puttytel). -GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint - + winutils wincfg sercfg winhelp winjump miscucs +GUITERM = TERMINAL window windlg winctrls sizetip winprint winutils + + wincfg sercfg winhelp winjump sessprep # Same thing on Unix. UXTERM = TERMINAL uxcfg sercfg uxucs uxprint timing callback miscucs GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym - + x11misc gtkcomm + + x11misc gtkcomm sessprep +GTKMAIN = gtkmain cmdline # Non-SSH back ends (putty, puttytel, plink). NONSSH = telnet raw rlogin ldisc pinger # SSH back end (putty, plink, pscp, psftp). -SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf - + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd - + sshaes sshccp sshsh256 sshsh512 sshbn wildcard pinger ssharcf - + sshgssc pgssapi sshshare sshecc aqsync +SSHCOMMON = sshcommon sshrand + + sshverstring sshcrc sshdes sshmd5 sshrsa sshrsacert sshsha sshblowf + + sshdh sshcrcda sshpubk sshzlib sshdss sshdsscert ssharcf + + sshaes sshccp sshsh256 sshsh512 sshbn sshmac marshal nullplug + + sshgssc pgssapi sshecc sshecccert wildcard ssh1censor ssh2censor ssh2bpp + + ssh2transport ssh2transhk ssh2connection portfwd x11fwd + + ssh1connection ssh1bpp +SSH = SSHCOMMON ssh ssh2bpp-bare + + 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 = sftp int64 logging - -# Miscellaneous objects appearing in all the network utilities (not -# Pageant or PuTTYgen). -MISC = timing callback misc version settings tree234 proxy conf be_misc -WINMISC = MISC winstore winnet winhandl cmdline windefs winmisc winproxy - + wintime winhsock errsock winsecur -UXMISC = MISC uxstore uxsel uxnet uxpeer cmdline uxmisc uxproxy time +SFTP = sftp sftpcommon logging cmdline + +# Miscellaneous objects appearing in all the utilities, or all the +# network ones, or the Unix or Windows subsets of those in turn. +MISC = misc marshal +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 +UXMISCCOMMON = MISCNETCOMMON uxstore uxsel uxnet uxpeer uxmisc time + + uxfdsock errsock +UXMISC = MISCNET UXMISCCOMMON uxproxy + +# 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 +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 comctl32.lib comdlg32.lib - + shell32.lib winmm.lib imm32.lib winspool.lib ole32.lib +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. @@ -298,62 +319,65 @@ U_BE_NOSSH = be_nos_s uxser nocproxy putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS plink : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC - + winx11 plink.res winnojmp noterm LIBS + + winx11 plink.res winnojmp sessprep noterm LIBS pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + pscp.res winnojmp LIBS psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + psftp.res winnojmp LIBS -pageant : [G] winpgnt pageant sshrsa sshpubk sshdes sshbn sshmd5 version - + tree234 misc sshaes sshsha winsecur winpgntc aqsync sshdss sshsh256 - + sshsh512 winutils sshecc winmisc winhelp conf pageant.res LIBS +pageant : [G] winpgnt pageant sshrsa sshrsacert sshpubk sshdes sshbn sshmd5 version + + tree234 MISC sshaes sshsha winsecur winpgntc aqsync sshdss sshdsscert sshsh256 + + sshsh512 winutils sshecc sshecccert winmisc winhelp conf pageant.res LIBS puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version - + sshrand winnoise sshsha winstore misc winctrls sshrsa sshdss winmisc + + sshrand winnoise sshsha winstore MISC winctrls sshrsa sshrsacert sshdss sshdsscert winmisc + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res - + tree234 notiming winhelp winnojmp conf LIBS wintime sshecc + + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc sshecccert + sshecdsag winsecur pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg - + nogss gtkmain + + nogss GTKMAIN putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg gtkmain + + xpmpucfg GTKMAIN puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg - + nogss gtkmain + + nogss GTKMAIN plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal - + ux_x11 noterm uxnogtk + + ux_x11 noterm uxnogtk sessprep cmdline PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes sshbn sshmd5 version - + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc + + sshrand uxnoise sshsha MISC sshrsa sshdss uxcons uxstore uxmisc + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234 - + uxgen notiming conf sshecc sshecdsag uxnogtk + + uxgen notiming CONF sshecc sshecccert sshecdsag uxnogtk puttygen : [U] cmdgen PUTTYGEN_UNIX cgtest : [UT] cgtest PUTTYGEN_UNIX pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk -pageant : [X] uxpgnt uxagentc aqsync pageant sshrsa sshpubk sshdes sshbn - + sshmd5 version tree234 misc sshaes sshsha sshdss sshsh256 sshsh512 - + sshecc conf uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons - + gtkask gtkmisc UXMISC +pageant : [X] uxpgnt uxagentc aqsync pageant sshrsa sshrsacert sshpubk sshdes sshbn + + sshmd5 version tree234 misc sshaes sshsha sshdss sshdsscert sshsh256 sshsh512 + + sshecc sshecccert CONF uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons + + gtkask gtkmisc nullplug logging UXMISC uxagentsock ptermapp : [XT] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore - + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg - + nogss gtkapp + + uxsignal CHARSET uxpterm version time xpmpterm xpmptcfg + + nogss gtkapp nocmdline puttyapp : [XT] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty - + xpmpucfg gtkapp + + xpmpucfg gtkapp nocmdline osxlaunch : [UT] osxlaunch fuzzterm : [UT] UXTERM CHARSET misc version uxmisc uxucs fuzzterm time settings + uxstore be_none uxnogtk -testbn : [UT] testbn sshbn misc version conf tree234 uxmisc uxnogtk -testbn : [C] testbn sshbn misc version conf tree234 winmisc LIBS +testbn : [UT] testbn sshbn MISC version CONF tree234 uxmisc uxnogtk +testbn : [C] testbn sshbn MISC version CONF tree234 winmisc LIBS + +uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk + + uxpty uxsftpserver ux_x11 uxagentsock # ---------------------------------------------------------------------- # On Windows, provide a means of removing local test binaries that we diff --git a/agentf.c b/agentf.c new file mode 100644 index 00000000..4b859f44 --- /dev/null +++ b/agentf.c @@ -0,0 +1,249 @@ +/* + * 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) +{ + unsigned 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(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 int agentf_send(Channel *chan, bool is_stderr, const void *, int); +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 struct ChannelVtable agentf_channelvt = { + agentf_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + agentf_send, + agentf_send_eof, + agentf_set_input_wanted, + agentf_log_close_msg, + chan_default_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + 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 int agentf_send(Channel *chan, bool is_stderr, + const void *data, int 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/aqsync.c b/aqsync.c index e16eadd1..dee048e7 100644 --- a/aqsync.c +++ b/aqsync.c @@ -11,11 +11,11 @@ #include "putty.h" -void agent_query_synchronous(void *in, int inlen, void **out, int *outlen) +void agent_query_synchronous(strbuf *query, void **out, int *outlen) { agent_pending_query *pending; - pending = agent_query(in, inlen, out, outlen, NULL, 0); + pending = agent_query(query, out, outlen, NULL, 0); assert(!pending); } diff --git a/be_all.c b/be_all.c index c58903cc..9673c798 100644 --- a/be_all.c +++ b/be_all.c @@ -22,7 +22,7 @@ const int be_default_protocol = PROT_TELNET; const int be_default_protocol = PROT_SSH; #endif -Backend *backends[] = { +const struct BackendVtable *const backends[] = { &ssh_backend, &telnet_backend, &rlogin_backend, diff --git a/be_all_s.c b/be_all_s.c index 0ffd0737..2f140ec0 100644 --- a/be_all_s.c +++ b/be_all_s.c @@ -22,7 +22,7 @@ const int be_default_protocol = PROT_TELNET; const int be_default_protocol = PROT_SSH; #endif -Backend *backends[] = { +const struct BackendVtable *const backends[] = { &ssh_backend, &telnet_backend, &rlogin_backend, diff --git a/be_misc.c b/be_misc.c index e479aa3b..79a6d33c 100644 --- a/be_misc.c +++ b/be_misc.c @@ -5,13 +5,13 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "putty.h" #include "network.h" -void backend_socket_log(void *frontend, int type, SockAddr addr, int port, +void backend_socket_log(Seat *seat, LogContext *logctx, + int type, SockAddr *addr, int port, const char *error_msg, int error_code, Conf *conf, - int session_started) + bool session_started) { char addrbuf[256], *msg; @@ -43,7 +43,7 @@ void backend_socket_log(void *frontend, int type, SockAddr addr, int port, if (log_to_term == AUTO) log_to_term = session_started ? FORCE_OFF : FORCE_ON; if (log_to_term == FORCE_ON) - from_backend(frontend, TRUE, msg, len); + seat_stderr(seat, msg, len); msg[len-2] = '\0'; /* remove the \r\n again */ } @@ -54,17 +54,18 @@ void backend_socket_log(void *frontend, int type, SockAddr addr, int port, } if (msg) { - logevent(frontend, msg); + logevent(logctx, msg); sfree(msg); } } -void log_proxy_stderr(Plug plug, bufchain *buf, const void *vdata, int len) +void log_proxy_stderr(Plug *plug, bufchain *buf, const void *vdata, int len) { const char *data = (const char *)vdata; int pos = 0; int msglen; - char *nlpos, *msg, *fullmsg; + const char *nlpos; + char *msg, *fullmsg; /* * This helper function allows us to collect the data written to a @@ -91,6 +92,8 @@ void log_proxy_stderr(Plug plug, bufchain *buf, const void *vdata, int len) msg = snewn(msglen+1, char); bufchain_fetch(buf, msg, msglen); bufchain_consume(buf, msglen); + while (msglen > 0 && (msg[msglen-1] == '\n' || msg[msglen-1] == '\r')) + msglen--; msg[msglen] = '\0'; fullmsg = dupprintf("proxy: %s", msg); plug_log(plug, 2, NULL, 0, fullmsg, 0); diff --git a/be_none.c b/be_none.c index 688b8daf..abc05517 100644 --- a/be_none.c +++ b/be_none.c @@ -6,6 +6,6 @@ #include #include "putty.h" -Backend *backends[] = { +const struct BackendVtable *const backends[] = { NULL }; diff --git a/be_nos_s.c b/be_nos_s.c index a574ead9..b3c61e7c 100644 --- a/be_nos_s.c +++ b/be_nos_s.c @@ -10,7 +10,7 @@ const int be_default_protocol = PROT_TELNET; const char *const appname = "PuTTYtel"; -Backend *backends[] = { +const struct BackendVtable *const backends[] = { &telnet_backend, &rlogin_backend, &raw_backend, diff --git a/be_nossh.c b/be_nossh.c index 33d783a8..daf15998 100644 --- a/be_nossh.c +++ b/be_nossh.c @@ -10,7 +10,7 @@ const int be_default_protocol = PROT_TELNET; const char *const appname = "PuTTYtel"; -Backend *backends[] = { +const struct BackendVtable *const backends[] = { &telnet_backend, &rlogin_backend, &raw_backend, diff --git a/be_ssh.c b/be_ssh.c index 57d241c2..40737e52 100644 --- a/be_ssh.c +++ b/be_ssh.c @@ -10,7 +10,7 @@ const int be_default_protocol = PROT_SSH; -Backend *backends[] = { +const struct BackendVtable *const backends[] = { &ssh_backend, NULL }; diff --git a/callback.c b/callback.c index c70dc53f..076d7b4d 100644 --- a/callback.c +++ b/callback.c @@ -14,16 +14,59 @@ struct callback { void *ctx; }; -struct callback *cbhead = NULL, *cbtail = NULL; +struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL; toplevel_callback_notify_fn_t notify_frontend = NULL; -void *frontend = NULL; +void *notify_ctx = NULL; void request_callback_notifications(toplevel_callback_notify_fn_t fn, - void *fr) + void *ctx) { notify_frontend = fn; - frontend = fr; + notify_ctx = ctx; +} + +static void run_idempotent_callback(void *ctx) +{ + struct IdempotentCallback *ic = (struct IdempotentCallback *)ctx; + ic->queued = false; + ic->fn(ic->ctx); +} + +void queue_idempotent_callback(struct IdempotentCallback *ic) +{ + if (ic->queued) + return; + ic->queued = true; + queue_toplevel_callback(run_idempotent_callback, ic); +} + +void delete_callbacks_for_context(void *ctx) +{ + struct callback *newhead, *newtail; + + newhead = newtail = NULL; + while (cbhead) { + struct callback *cb = cbhead; + cbhead = cbhead->next; + if (cb->ctx == ctx || + (cb->fn == run_idempotent_callback && + ((struct IdempotentCallback *)cb->ctx)->ctx == ctx)) { + sfree(cb); + } else { + if (!newhead) + newhead = cb; + else + newtail->next = cb; + + newtail = cb; + } + } + + cbhead = newhead; + cbtail = newtail; + if (newtail) + newtail->next = NULL; } void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) @@ -34,11 +77,19 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) cb->fn = fn; cb->ctx = ctx; - /* If the front end has requested notification of pending + /* + * If the front end has requested notification of pending * callbacks, and we didn't already have one queued, let it know - * we do have one now. */ - if (notify_frontend && !cbhead) - notify_frontend(frontend); + * we do have one now. + * + * If cbcurr is non-NULL, i.e. we are actually in the middle of + * executing a callback right now, then we count that as the queue + * already having been non-empty. That saves the front end getting + * a constant stream of needless re-notifications if the last + * callback keeps re-scheduling itself. + */ + if (notify_frontend && !cbhead && !cbcurr) + notify_frontend(notify_ctx); if (cbtail) cbtail->next = cb; @@ -48,27 +99,35 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) cb->next = NULL; } -void run_toplevel_callbacks(void) +bool run_toplevel_callbacks(void) { + bool done_something = false; + if (cbhead) { - struct callback *cb = cbhead; /* - * Careful ordering here. We call the function _before_ - * advancing cbhead (though, of course, we must free cb - * _after_ advancing it). This means that if the very last - * callback schedules another callback, cbhead does not become - * NULL at any point, and so the frontend notification - * function won't be needlessly pestered. + * Transfer the head callback into cbcurr to indicate that + * it's being executed. Then operations which transform the + * queue, like delete_callbacks_for_context, can proceed as if + * it's not there. */ - cb->fn(cb->ctx); - cbhead = cb->next; - sfree(cb); + cbcurr = cbhead; + cbhead = cbhead->next; if (!cbhead) cbtail = NULL; + + /* + * Now run the callback, and then clear it out of cbcurr. + */ + cbcurr->fn(cbcurr->ctx); + sfree(cbcurr); + cbcurr = NULL; + + done_something = true; } + return done_something; } -int toplevel_callback_pending(void) +bool toplevel_callback_pending(void) { - return cbhead != NULL; + return cbcurr != NULL || cbhead != NULL; } diff --git a/charset/utf8.c b/charset/utf8.c index 3c777292..13c5baa8 100644 --- a/charset/utf8.c +++ b/charset/utf8.c @@ -7,18 +7,13 @@ #include "charset.h" #include "internal.h" -void read_utf8(charset_spec const *, long int, charset_state *, - void (*)(void *, long int), void *); -void write_utf8(charset_spec const *, long int, - charset_state *, void (*)(void *, long int), void *); - /* * UTF-8 has no associated data, so `charset' may be ignored. */ -void read_utf8(charset_spec const *charset, long int input_chr, - charset_state *state, - void (*emit)(void *ctx, long int output), void *emitctx) +static void read_utf8(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) { UNUSEDARG(charset); @@ -186,9 +181,9 @@ void read_utf8(charset_spec const *charset, long int input_chr, * charset_state. */ -void write_utf8(charset_spec const *charset, long int input_chr, - charset_state *state, - void (*emit)(void *ctx, long int output), void *emitctx) +static void write_utf8(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) { UNUSEDARG(charset); UNUSEDARG(state); @@ -267,7 +262,7 @@ void utf8_read_test(int line, char *input, int inlen, ...) } if (l != str[i]) { printf("%d: char %d came out as %08x, should be %08x\n", - line, i, str[i], l); + line, i, str[i], (unsigned)l); total_errs++; } } @@ -306,7 +301,7 @@ void utf8_write_test(int line, const long *input, int inlen, ...) } if (l != str[i]) { printf("%d: char %d came out as %08x, should be %08x\n", - line, i, str[i], l); + line, i, str[i], (unsigned)l); total_errs++; } } diff --git a/cmdgen.c b/cmdgen.c index 9d9d011a..4ef972f3 100644 --- a/cmdgen.c +++ b/cmdgen.c @@ -46,7 +46,7 @@ char *get_random_data(int len, const char *device) #define console_get_userpass_input console_get_userpass_input_diagnostic int nprompts, promptsgot; const char *prompts[3]; -int console_get_userpass_input(prompts_t *p, unsigned char *in, int inlen) +int console_get_userpass_input(prompts_t *p) { size_t i; int ret = 1; @@ -92,33 +92,9 @@ static void no_progress(void *param, int action, int phase, int iprogress) { } -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); - cleanup_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); -} - /* * Stubs to let everything else link sensibly. */ -void log_eventlog(void *handle, const char *event) -{ -} char *x_get_default(const char *key) { return NULL; @@ -126,6 +102,10 @@ char *x_get_default(const char *key) void sk_cleanup(void) { } +void queue_idempotent_callback(IdempotentCallback *ic) +{ + assert(0); +} void showversion(void) { @@ -134,7 +114,7 @@ void showversion(void) sfree(buildinfo_text); } -void usage(int standalone) +void usage(bool standalone) { fprintf(standalone ? stderr : stdout, "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n" @@ -154,7 +134,7 @@ void help(void) */ printf("PuTTYgen: key generator and converter for the PuTTY tools\n" "%s\n", ver); - usage(FALSE); + usage(false); printf(" -t specify key type when generating (ed25519, ecdsa, rsa, " "dsa, rsa1)\n" " -b specify number of bits when generating key\n" @@ -183,7 +163,7 @@ void help(void) ); } -static int move(char *from, char *to) +static bool move(char *from, char *to) { int ret; @@ -197,9 +177,9 @@ static int move(char *from, char *to) } if (ret) { perror("puttygen: cannot move new file on to old one"); - return FALSE; + return false; } - return TRUE; + return true; } static char *readpassphrase(const char *filename) @@ -228,7 +208,7 @@ static char *readpassphrase(const char *filename) #define DEFAULT_RSADSA_BITS 2048 /* For Unix in particular, but harmless if this main() is reused elsewhere */ -const int buildinfo_gtk_relevant = FALSE; +const bool buildinfo_gtk_relevant = false; int main(int argc, char **argv) { @@ -240,18 +220,16 @@ int main(int argc, char **argv) OPENSSH_NEW, SSHCOM } outtype = PRIVATE; int bits = -1; char *comment = NULL, *origcomment = NULL; - int change_passphrase = FALSE; - int errs = FALSE, nogo = FALSE; + bool change_passphrase = false; + bool errs = false, nogo = false; int intype = SSH_KEYTYPE_UNOPENABLE; int sshver = 0; struct ssh2_userkey *ssh2key = NULL; struct RSAKey *ssh1key = NULL; - unsigned char *ssh2blob = NULL; + strbuf *ssh2blob = NULL; char *ssh2alg = NULL; - const struct ssh_signkey *ssh2algf = NULL; - int ssh2bloblen; char *old_passphrase = NULL, *new_passphrase = NULL; - int load_encrypted; + bool load_encrypted; progfn_t progressfn = is_interactive() ? progress_update : no_progress; const char *random_device = NULL; @@ -264,7 +242,7 @@ int main(int argc, char **argv) * return success. */ if (argc <= 1) { - usage(TRUE); + usage(true); return 0; } @@ -297,68 +275,68 @@ int main(int argc, char **argv) if (!strcmp(opt, "-help")) { if (val) { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: option `-%s'" " expects no argument\n", opt); } else { help(); - nogo = TRUE; + nogo = true; } } else if (!strcmp(opt, "-version")) { if (val) { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: option `-%s'" " expects no argument\n", opt); } else { showversion(); - nogo = TRUE; + nogo = true; } } else if (!strcmp(opt, "-pgpfp")) { if (val) { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: option `-%s'" " expects no argument\n", opt); } else { /* support --pgpfp for consistency */ pgp_fingerprints(); - nogo = TRUE; + nogo = true; } } else if (!strcmp(opt, "-old-passphrase")) { if (!val && argc > 1) --argc, val = *++argv; if (!val) { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: option `-%s'" " expects an argument\n", opt); } else { old_passphrase = readpassphrase(val); if (!old_passphrase) - errs = TRUE; + errs = true; } } else if (!strcmp(opt, "-new-passphrase")) { if (!val && argc > 1) --argc, val = *++argv; if (!val) { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: option `-%s'" " expects an argument\n", opt); } else { new_passphrase = readpassphrase(val); if (!new_passphrase) - errs = TRUE; + errs = true; } } else if (!strcmp(opt, "-random-device")) { if (!val && argc > 1) --argc, val = *++argv; if (!val) { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: option `-%s'" " expects an argument\n", opt); } else { random_device = val; } } else { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: no such option `-%s'\n", opt); } @@ -378,14 +356,14 @@ int main(int argc, char **argv) switch (c) { case 'h': help(); - nogo = TRUE; + nogo = true; break; case 'V': showversion(); - nogo = TRUE; + nogo = true; break; case 'P': - change_passphrase = TRUE; + change_passphrase = true; break; case 'l': outtype = FP; @@ -415,7 +393,7 @@ int main(int argc, char **argv) else if (!*p) { fprintf(stderr, "puttygen: option `-%c' expects a" " parameter\n", c); - errs = TRUE; + errs = true; } /* * Now c is the option and p is the parameter. @@ -435,7 +413,7 @@ int main(int argc, char **argv) else { fprintf(stderr, "puttygen: unknown key type `%s'\n", p); - errs = TRUE; + errs = true; } break; case 'b': @@ -462,7 +440,7 @@ int main(int argc, char **argv) else { fprintf(stderr, "puttygen: unknown output type `%s'\n", p); - errs = TRUE; + errs = true; } break; case 'o': @@ -475,7 +453,7 @@ int main(int argc, char **argv) /* * Unrecognised option. */ - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: no such option `-%c'\n", c); break; } @@ -487,7 +465,7 @@ int main(int argc, char **argv) if (!infile) infile = p; else { - errs = TRUE; + errs = true; fprintf(stderr, "puttygen: cannot handle more than one" " input file\n"); } @@ -514,19 +492,19 @@ int main(int argc, char **argv) if (keytype == ECDSA && (bits != 256 && bits != 384 && bits != 521)) { fprintf(stderr, "puttygen: invalid bits for ECDSA, choose 256, 384 or 521\n"); - errs = TRUE; + errs = true; } if (keytype == ED25519 && (bits != 256)) { fprintf(stderr, "puttygen: invalid bits for ED25519, choose 256\n"); - errs = TRUE; + errs = true; } if (keytype == RSA2 || keytype == RSA1 || keytype == DSA) { if (bits < 256) { fprintf(stderr, "puttygen: cannot generate %s keys shorter than" " 256 bits\n", (keytype == DSA ? "DSA" : "RSA")); - errs = TRUE; + errs = true; } else if (bits < DEFAULT_RSADSA_BITS) { fprintf(stderr, "puttygen: warning: %s keys shorter than" " %d bits are probably not secure\n", @@ -546,7 +524,7 @@ int main(int argc, char **argv) * ones, print the usage message and return failure. */ if (!infile && keytype == NOKEYGEN) { - usage(TRUE); + usage(true); return 1; } @@ -670,9 +648,9 @@ int main(int argc, char **argv) intype == SSH_KEYTYPE_OPENSSH_PEM || intype == SSH_KEYTYPE_OPENSSH_NEW || intype == SSH_KEYTYPE_SSHCOM) - load_encrypted = TRUE; + load_encrypted = true; else - load_encrypted = FALSE; + load_encrypted = false; if (load_encrypted && (intype == SSH_KEYTYPE_SSH1_PUBLIC || intype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || @@ -723,22 +701,19 @@ int main(int argc, char **argv) struct dss_key *dsskey = snew(struct dss_key); dsa_generate(dsskey, bits, progressfn, &prog); ssh2key = snew(struct ssh2_userkey); - ssh2key->data = dsskey; - ssh2key->alg = &ssh_dss; + ssh2key->key = &dsskey->sshk; ssh1key = NULL; } else if (keytype == ECDSA) { struct ec_key *ec = snew(struct ec_key); ec_generate(ec, bits, progressfn, &prog); ssh2key = snew(struct ssh2_userkey); - ssh2key->data = ec; - ssh2key->alg = ec->signalg; + ssh2key->key = &ec->sshk; ssh1key = NULL; } else if (keytype == ED25519) { struct ec_key *ec = snew(struct ec_key); ec_edgenerate(ec, bits, progressfn, &prog); ssh2key = snew(struct ssh2_userkey); - ssh2key->data = ec; - ssh2key->alg = &ssh_ecdsa_ed25519; + ssh2key->key = &ec->sshk; ssh1key = NULL; } else { struct RSAKey *rsakey = snew(struct RSAKey); @@ -748,8 +723,7 @@ int main(int argc, char **argv) ssh1key = rsakey; } else { ssh2key = snew(struct ssh2_userkey); - ssh2key->data = rsakey; - ssh2key->alg = &ssh_rsa; + ssh2key->key = &rsakey->sshk; } } progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1); @@ -761,7 +735,7 @@ int main(int argc, char **argv) } else { const char *error = NULL; - int encrypted; + bool encrypted; assert(infile != NULL); @@ -769,7 +743,7 @@ int main(int argc, char **argv) * Find out whether the input key is encrypted. */ if (intype == SSH_KEYTYPE_SSH1) - encrypted = rsakey_encrypted(infilename, &origcomment); + encrypted = rsa_ssh1_encrypted(infilename, &origcomment); else if (intype == SSH_KEYTYPE_SSH2) encrypted = ssh2_userkey_encrypted(infilename, &origcomment); else @@ -780,12 +754,12 @@ int main(int argc, char **argv) */ if (encrypted && load_encrypted) { if (!old_passphrase) { - prompts_t *p = new_prompts(NULL); + prompts_t *p = new_prompts(); int ret; - p->to_server = FALSE; + p->to_server = false; p->name = dupstr("SSH key passphrase"); - add_prompt(p, dupstr("Enter passphrase to load key: "), FALSE); - ret = console_get_userpass_input(p, NULL, 0); + add_prompt(p, dupstr("Enter passphrase to load key: "), false); + ret = console_get_userpass_input(p); assert(ret >= 0); if (!ret) { free_prompts(p); @@ -807,36 +781,24 @@ int main(int argc, char **argv) case SSH_KEYTYPE_SSH1_PUBLIC: ssh1key = snew(struct RSAKey); if (!load_encrypted) { - void *vblob; - unsigned char *blob; - int n, l, bloblen; - - ret = rsakey_pubblob(infilename, &vblob, &bloblen, - &origcomment, &error); - blob = (unsigned char *)vblob; - - n = 4; /* skip modulus bits */ - - l = ssh1_read_bignum(blob + n, bloblen - n, - &ssh1key->exponent); - if (l < 0) { - error = "SSH-1 public key blob was too short"; - } else { - n += l; - l = ssh1_read_bignum(blob + n, bloblen - n, - &ssh1key->modulus); - if (l < 0) { - error = "SSH-1 public key blob was too short"; - } else - n += l; - } + strbuf *blob; + BinarySource src[1]; + + blob = strbuf_new(); + ret = rsa_ssh1_loadpub(infilename, BinarySink_UPCAST(blob), + &origcomment, &error); + BinarySource_BARE_INIT(src, blob->u, blob->len); + get_rsa_ssh1_pub(src, ssh1key, RSA_SSH1_EXPONENT_FIRST); + strbuf_free(blob); + ssh1key->comment = dupstr(origcomment); ssh1key->private_exponent = NULL; ssh1key->p = NULL; ssh1key->q = NULL; ssh1key->iqmp = NULL; } else { - ret = loadrsakey(infilename, ssh1key, old_passphrase, &error); + ret = rsa_ssh1_loadkey( + infilename, ssh1key, old_passphrase, &error); } if (ret > 0) error = NULL; @@ -848,16 +810,17 @@ int main(int argc, char **argv) case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716: case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: if (!load_encrypted) { - ssh2blob = ssh2_userkey_loadpub(infilename, &ssh2alg, - &ssh2bloblen, &origcomment, - &error); - if (ssh2blob) { - ssh2algf = find_pubkey_alg(ssh2alg); - if (ssh2algf) - bits = ssh2algf->pubkey_bits(ssh2algf, - ssh2blob, ssh2bloblen); + ssh2blob = strbuf_new(); + if (ssh2_userkey_loadpub(infilename, &ssh2alg, BinarySink_UPCAST(ssh2blob), + &origcomment, &error)) { + const ssh_keyalg *alg = find_pubkey_alg(ssh2alg); + if (alg) + bits = ssh_key_public_bits( + alg, ptrlen_from_strbuf(ssh2blob)); else bits = -1; + } else { + strbuf_free(ssh2blob); } sfree(ssh2alg); } else { @@ -928,11 +891,11 @@ int main(int argc, char **argv) prompts_t *p = new_prompts(NULL); int ret; - p->to_server = FALSE; + p->to_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, NULL, 0); + 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) { free_prompts(p); @@ -967,12 +930,13 @@ int main(int argc, char **argv) outfilename = filename_from_str(outfile ? outfile : ""); switch (outtype) { - int ret, real_outtype; + bool ret; + int real_outtype; case PRIVATE: if (sshver == 1) { assert(ssh1key); - ret = saversakey(outfilename, ssh1key, new_passphrase); + ret = rsa_ssh1_savekey(outfilename, ssh1key, new_passphrase); if (!ret) { fprintf(stderr, "puttygen: unable to save SSH-1 private key\n"); return 1; @@ -996,22 +960,27 @@ int main(int argc, char **argv) { FILE *fp; - if (outfile) - fp = f_open(outfilename, "w", FALSE); - else + if (outfile) { + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } + } else { fp = stdout; + } if (sshver == 1) { ssh1_write_pubkey(fp, ssh1key); } else { if (!ssh2blob) { assert(ssh2key); - ssh2blob = ssh2key->alg->public_blob(ssh2key->data, - &ssh2bloblen); + ssh2blob = strbuf_new(); + ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); } ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, - ssh2blob, ssh2bloblen, + ssh2blob->s, ssh2blob->len, (outtype == PUBLIC ? SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); @@ -1029,22 +998,26 @@ int main(int argc, char **argv) if (sshver == 1) { assert(ssh1key); - fingerprint = snewn(128, char); - rsa_fingerprint(fingerprint, 128, ssh1key); + fingerprint = rsa_ssh1_fingerprint(ssh1key); } else { if (ssh2key) { - fingerprint = ssh2_fingerprint(ssh2key->alg, - ssh2key->data); + fingerprint = ssh2_fingerprint(ssh2key->key); } else { assert(ssh2blob); - fingerprint = ssh2_fingerprint_blob(ssh2blob, ssh2bloblen); + fingerprint = ssh2_fingerprint_blob( + ssh2blob->s, ssh2blob->len); } } - if (outfile) - fp = f_open(outfilename, "w", FALSE); - else + if (outfile) { + fp = f_open(outfilename, "w", false); + if (!fp) { + fprintf(stderr, "unable to open output file\n"); + exit(1); + } + } else { fp = stdout; + } fprintf(fp, "%s\n", fingerprint); if (outfile) fclose(fp); @@ -1097,7 +1070,7 @@ int main(int argc, char **argv) if (ssh1key) freersakey(ssh1key); if (ssh2key) { - ssh2key->alg->freekey(ssh2key->data); + ssh_key_free(ssh2key->key); sfree(ssh2key); } @@ -1299,14 +1272,15 @@ int main(int argc, char **argv) setup_passphrases(NULL); test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL); { - char cmdbuf[256]; + char *cmdbuf; fp = NULL; - sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'", + cmdbuf = dupprintf("ssh-keygen -l -f '%s' > '%s'", pubfilename, tmpfilename1); if (system(cmdbuf) || (fp = get_fp(tmpfilename1)) == NULL) { printf("UNABLE to test fingerprint matching against OpenSSH"); } + sfree(cmdbuf); } /* diff --git a/cmdline.c b/cmdline.c index f288ed62..f71a7763 100644 --- a/cmdline.c +++ b/cmdline.c @@ -81,20 +81,20 @@ void cmdline_cleanup(void) } while (0) /* - * Similar interface to get_userpass_input(), except that here a -1 - * return means that we aren't capable of processing the prompt and + * Similar interface to seat_get_userpass_input(), except that here a + * -1 return means that we aren't capable of processing the prompt and * someone else should do it. */ -int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen) +int cmdline_get_passwd_input(prompts_t *p) { - static int tried_once = 0; + 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 * that comes in a prompt-set on its own. */ - if (!cmdline_password || in || p->n_prompts != 1 || p->prompts[0]->echo) { + if (!cmdline_password || p->n_prompts != 1 || p->prompts[0]->echo) { return -1; } @@ -109,7 +109,7 @@ int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen) smemclr(cmdline_password, strlen(cmdline_password)); sfree(cmdline_password); cmdline_password = NULL; - tried_once = 1; + tried_once = true; return 1; } @@ -125,13 +125,13 @@ int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen) */ int cmdline_tooltype = 0; -static int cmdline_check_unavailable(int flag, const char *p) +static bool cmdline_check_unavailable(int flag, const char *p) { if (cmdline_tooltype & flag) { cmdline_error("option \"%s\" not available in this tool", p); - return 1; + return true; } - return 0; + return false; } #define UNAVAILABLE_IN(flag) do { \ @@ -159,17 +159,249 @@ static int cmdline_check_unavailable(int flag, const char *p) if (need_save < 0) return x; \ } while (0) +static bool seen_hostname_argument = false; +static bool seen_port_argument = false; + int cmdline_process_param(const char *p, char *value, int need_save, Conf *conf) { int ret = 0; + if (p[0] != '-') { + if (need_save < 0) + return 0; + + /* + * Common handling for the tools whose initial command-line + * arguments specify a hostname to connect to, i.e. PuTTY and + * Plink. Doesn't count the file transfer tools, because their + * hostname specification appears as part of a more + * complicated scheme. + */ + + if ((cmdline_tooltype & TOOLTYPE_HOST_ARG) && + !seen_hostname_argument && + (!(cmdline_tooltype & TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) || + !loaded_session || !conf_launchable(conf))) { + /* + * Treat this argument as a host name, if we have not yet + * seen a host name argument or -load. + * + * Exception, in some tools (Plink): if we have seen -load + * but it didn't create a launchable session, then we + * still accept a hostname argument following that -load. + * This allows you to make saved sessions that configure + * lots of other stuff (colour schemes, terminal settings + * etc) and then say 'putty -load sessionname hostname'. + * + * Also, we carefully _don't_ test conf for launchability + * if we haven't been explicitly told to load a session + * (otherwise saving a host name into Default Settings + * would cause 'putty' on its own to immediately launch + * the default session and never be able to do anything + * else). + */ + if (!strncmp(p, "telnet:", 7)) { + /* + * If the argument starts with "telnet:", set the + * protocol to Telnet and process the string as a + * Telnet URL. + */ + + /* + * Skip the "telnet:" or "telnet://" prefix. + */ + p += 7; + if (p[0] == '/' && p[1] == '/') + p += 2; + conf_set_int(conf, CONF_protocol, PROT_TELNET); + + /* + * The next thing we expect is a host name. + */ + { + const char *host = p; + char *buf; + + p += host_strcspn(p, ":/"); + buf = dupprintf("%.*s", (int)(p - host), host); + conf_set_str(conf, CONF_host, buf); + sfree(buf); + seen_hostname_argument = true; + } + + /* + * If the host name is followed by a colon, then + * expect a port number after it. + */ + if (*p == ':') { + p++; + + conf_set_int(conf, CONF_port, atoi(p)); + /* + * Set the flag that will stop us from treating + * the next argument as a separate port; this one + * counts as explicitly provided. + */ + seen_port_argument = true; + } else { + conf_set_int(conf, CONF_port, -1); + } + } else { + char *user = NULL, *hostname = NULL; + const char *hostname_after_user; + int port_override = -1; + size_t len; + + /* + * Otherwise, treat it as a bare host name. + */ + + if (cmdline_tooltype & TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX) { + /* + * Here Plink checks for a comma-separated + * protocol prefix, e.g. 'ssh,hostname' or + * 'ssh,user@hostname'. + * + * I'm not entirely sure why; this behaviour dates + * from 2000 and isn't explained. But I _think_ it + * has to do with CVS transport or similar use + * cases, in which the end user invokes the SSH + * client indirectly, via some means that only + * lets them pass a single string argument, and it + * was occasionally useful to shoehorn the choice + * of protocol into that argument. + */ + const char *comma = strchr(p, ','); + if (comma) { + char *prefix = dupprintf("%.*s", (int)(comma - p), p); + const struct BackendVtable *vt = + backend_vt_from_name(prefix); + + if (vt) { + default_protocol = vt->protocol; + conf_set_int(conf, CONF_protocol, + default_protocol); + port_override = vt->default_port; + } else { + cmdline_error("unrecognised protocol prefix '%s'", + prefix); + } + + sfree(prefix); + p = comma + 1; + } + } + + hostname_after_user = p; + if (cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) { + /* + * If the hostname argument can also be a saved + * session (see below), then here we also check + * for a user@ prefix, which will override the + * username from the saved session. + * + * (If the hostname argument _isn't_ a saved + * session, we don't do this.) + */ + const char *at = strrchr(p, '@'); + if (at) { + user = dupprintf("%.*s", (int)(at - p), p); + hostname_after_user = at + 1; + } + } + + /* + * Write the whole hostname argument (minus only that + * optional protocol prefix) into the existing Conf, + * for tools that don't treat it as a saved session + * and as a fallback for those that do. + */ + hostname = dupstr(p + strspn(p, " \t")); + len = strlen(hostname); + while (len > 0 && (hostname[len-1] == ' ' || + hostname[len-1] == '\t')) + hostname[--len] = '\0'; + seen_hostname_argument = true; + conf_set_str(conf, CONF_host, hostname); + + if ((cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) && + !loaded_session) { + /* + * For some tools, we equivocate between a + * hostname argument and an argument naming a + * saved session. Here we attempt to load a + * session with the specified name, and if that + * succeeds, we overwrite the entire Conf with it. + * + * We skip this check if a -load option has + * already happened, so that + * + * plink -load non-launchable-session hostname + * + * will treat 'hostname' as a hostname _even_ if a + * saved session called 'hostname' exists. (This + * doesn't lose any functionality someone could + * have needed, because if 'hostname' did cause a + * session to be loaded, then it would overwrite + * everything from the previously loaded session. + * So if that was the behaviour someone wanted, + * then they could get it by leaving off the + * -load completely.) + */ + Conf *conf2 = conf_new(); + do_defaults(hostname_after_user, conf2); + if (conf_launchable(conf2)) { + conf_copy_into(conf, conf2); + loaded_session = true; + /* And override the username if one was given. */ + if (user) + conf_set_str(conf, CONF_username, user); + } + conf_free(conf2); + } + + sfree(hostname); + sfree(user); + + if (port_override >= 0) + conf_set_int(conf, CONF_port, port_override); + } + + return 1; + } else if ((cmdline_tooltype & TOOLTYPE_PORT_ARG) && + !seen_port_argument) { + /* + * If we've already got a host name from the command line + * (either as a hostname argument or a qualifying -load), + * but not a port number, then treat the next argument as + * a port number. + * + * We handle this by calling ourself recursively to + * pretend we received a -P argument, so that it will be + * deferred until it's a good moment to run it. + */ + char *dup = dupstr(p); /* 'value' is not a const char * */ + int retd = cmdline_process_param("-P", dup, 1, conf); + sfree(dup); + assert(retd == 2); + seen_port_argument = true; + return 1; + } else { + /* + * Refuse to recognise this argument, and give it back to + * the tool's own command-line processing. + */ + return 0; + } + } + if (!strcmp(p, "-load")) { RETURN(2); /* This parameter must be processed immediately rather than being * saved. */ do_defaults(value, conf); - loaded_session = TRUE; + loaded_session = true; cmdline_session_name = dupstr(value); return 2; } @@ -362,7 +594,7 @@ int cmdline_process_param(const char *p, char *value, fclose(fp); conf_set_str(conf, CONF_remote_cmd, command); conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_int(conf, CONF_nopty, TRUE); /* command => no terminal */ + conf_set_bool(conf, CONF_nopty, true); /* command => no terminal */ sfree(command); } if (!strcmp(p, "-P")) { @@ -394,67 +626,78 @@ int cmdline_process_param(const char *p, char *value, RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_tryagent, TRUE); + conf_set_bool(conf, CONF_tryagent, true); } if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") || !strcmp(p, "-nopageant")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_tryagent, FALSE); + conf_set_bool(conf, CONF_tryagent, false); + } + if (!strcmp(p, "-share")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_bool(conf, CONF_ssh_connection_sharing, true); + } + if (!strcmp(p, "-noshare")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_bool(conf, CONF_ssh_connection_sharing, false); } - if (!strcmp(p, "-A")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_agentfwd, 1); + conf_set_bool(conf, CONF_agentfwd, true); } if (!strcmp(p, "-a")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_agentfwd, 0); + conf_set_bool(conf, CONF_agentfwd, false); } if (!strcmp(p, "-X")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_x11_forward, 1); + conf_set_bool(conf, CONF_x11_forward, true); } if (!strcmp(p, "-x")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_x11_forward, 0); + conf_set_bool(conf, CONF_x11_forward, false); } if (!strcmp(p, "-t")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); /* lower priority than -m */ - conf_set_int(conf, CONF_nopty, 0); + conf_set_bool(conf, CONF_nopty, false); } if (!strcmp(p, "-T")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(1); - conf_set_int(conf, CONF_nopty, 1); + conf_set_bool(conf, CONF_nopty, true); } if (!strcmp(p, "-N")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_ssh_no_shell, 1); + conf_set_bool(conf, CONF_ssh_no_shell, true); } if (!strcmp(p, "-C")) { RETURN(1); UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); SAVEABLE(0); - conf_set_int(conf, CONF_compression, 1); + conf_set_bool(conf, CONF_compression, true); } if (!strcmp(p, "-1")) { @@ -617,7 +860,7 @@ int cmdline_process_param(const char *p, char *value, !strcmp(p, "-restrictacl")) { RETURN(1); restrict_process_acl(); - restricted_acl = TRUE; + restricted_acl = true; } #endif @@ -637,3 +880,35 @@ void cmdline_run_saved(Conf *conf) saves[pri].nsaved = 0; } } + +bool cmdline_host_ok(Conf *conf) +{ + /* + * Return true if the command-line arguments we've processed in + * TOOLTYPE_HOST_ARG mode are sufficient to justify launching a + * session. + */ + assert(cmdline_tooltype & TOOLTYPE_HOST_ARG); + + /* + * Of course, if we _can't_ launch a session, the answer is + * clearly no. + */ + if (!conf_launchable(conf)) + return false; + + /* + * But also, if we haven't seen either a -load option or a + * hostname argument, i.e. the only saved settings we've loaded + * are Default Settings plus any non-hostname-based stuff from the + * command line, then the answer is still no, _even_ if this Conf + * is launchable. Otherwise, if you saved your favourite hostname + * into Default Settings, then just running 'putty' without + * arguments would connect to it without ever offering you the + * option to connect to something else or change the setting. + */ + if (!seen_hostname_argument && !loaded_session) + return false; + + return true; +} diff --git a/conf.c b/conf.c index 92341758..f97b47df 100644 --- a/conf.c +++ b/conf.c @@ -13,7 +13,9 @@ /* * Enumeration of types used in keys and values. */ -typedef enum { TYPE_NONE, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT } Type; +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 @@ -51,6 +53,7 @@ struct constkey { struct value { union { + bool boolval; int intval; char *stringval; Filename *fileval; @@ -171,6 +174,9 @@ static void free_value(struct value *val, int type) 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; @@ -256,6 +262,19 @@ Conf *conf_copy(Conf *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; @@ -384,6 +403,17 @@ FontSpec *conf_get_fontspec(Conf *conf, int primary) 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); @@ -391,11 +421,12 @@ void conf_set_int(Conf *conf, int primary, int value) assert(subkeytypes[primary] == TYPE_NONE); assert(valuetypes[primary] == TYPE_INT); entry->key.primary = primary; - entry->value.u.intval = value; + entry->value.u.intval = value; conf_insert(conf, entry); } -void conf_set_int_int(Conf *conf, int primary, int secondary, int value) +void conf_set_int_int(Conf *conf, int primary, + int secondary, int value) { struct conf_entry *entry = snew(struct conf_entry); @@ -469,179 +500,94 @@ void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) conf_insert(conf, entry); } -int conf_serialised_size(Conf *conf) +void conf_serialise(BinarySink *bs, Conf *conf) { int i; struct conf_entry *entry; - int size = 0; for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { - size += 4; /* primary key */ - switch (subkeytypes[entry->key.primary]) { - case TYPE_INT: - size += 4; - break; - case TYPE_STR: - size += 1 + strlen(entry->key.secondary.s); - break; - } - switch (valuetypes[entry->key.primary]) { - case TYPE_INT: - size += 4; - break; - case TYPE_STR: - size += 1 + strlen(entry->value.u.stringval); - break; - case TYPE_FILENAME: - size += filename_serialise(entry->value.u.fileval, NULL); - break; - case TYPE_FONT: - size += fontspec_serialise(entry->value.u.fontval, NULL); - break; - } - } - - size += 4; /* terminator value */ - - return size; -} - -void conf_serialise(Conf *conf, void *vdata) -{ - unsigned char *data = (unsigned char *)vdata; - int i, len; - struct conf_entry *entry; - - for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { - PUT_32BIT_MSB_FIRST(data, entry->key.primary); - data += 4; + put_uint32(bs, entry->key.primary); switch (subkeytypes[entry->key.primary]) { case TYPE_INT: - PUT_32BIT_MSB_FIRST(data, entry->key.secondary.i); - data += 4; + put_uint32(bs, entry->key.secondary.i); break; case TYPE_STR: - len = strlen(entry->key.secondary.s); - memcpy(data, entry->key.secondary.s, len); - data += len; - *data++ = 0; + 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_32BIT_MSB_FIRST(data, entry->value.u.intval); - data += 4; + put_uint32(bs, entry->value.u.intval); break; case TYPE_STR: - len = strlen(entry->value.u.stringval); - memcpy(data, entry->value.u.stringval, len); - data += len; - *data++ = 0; + put_asciz(bs, entry->value.u.stringval); break; case TYPE_FILENAME: - data += filename_serialise(entry->value.u.fileval, data); + filename_serialise(bs, entry->value.u.fileval); break; case TYPE_FONT: - data += fontspec_serialise(entry->value.u.fontval, data); + fontspec_serialise(bs, entry->value.u.fontval); break; } } - PUT_32BIT_MSB_FIRST(data, 0xFFFFFFFFU); + put_uint32(bs, 0xFFFFFFFFU); } -int conf_deserialise(Conf *conf, void *vdata, int maxsize) +bool conf_deserialise(Conf *conf, BinarySource *src) { - unsigned char *data = (unsigned char *)vdata; - unsigned char *start = data; struct conf_entry *entry; unsigned primary; - int used; - unsigned char *zero; - while (maxsize >= 4) { - primary = GET_32BIT_MSB_FIRST(data); - data += 4, maxsize -= 4; + while (1) { + primary = get_uint32(src); + if (get_err(src)) + return false; + if (primary == 0xFFFFFFFFU) + return true; if (primary >= N_CONFIG_OPTIONS) - break; + return false; entry = snew(struct conf_entry); entry->key.primary = primary; switch (subkeytypes[entry->key.primary]) { case TYPE_INT: - if (maxsize < 4) { - sfree(entry); - goto done; - } - entry->key.secondary.i = toint(GET_32BIT_MSB_FIRST(data)); - data += 4, maxsize -= 4; + entry->key.secondary.i = toint(get_uint32(src)); break; case TYPE_STR: - zero = memchr(data, 0, maxsize); - if (!zero) { - sfree(entry); - goto done; - } - entry->key.secondary.s = dupstr((char *)data); - maxsize -= (zero + 1 - data); - data = zero + 1; + 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: - if (maxsize < 4) { - if (subkeytypes[entry->key.primary] == TYPE_STR) - sfree(entry->key.secondary.s); - sfree(entry); - goto done; - } - entry->value.u.intval = toint(GET_32BIT_MSB_FIRST(data)); - data += 4, maxsize -= 4; + entry->value.u.intval = toint(get_uint32(src)); break; case TYPE_STR: - zero = memchr(data, 0, maxsize); - if (!zero) { - if (subkeytypes[entry->key.primary] == TYPE_STR) - sfree(entry->key.secondary.s); - sfree(entry); - goto done; - } - entry->value.u.stringval = dupstr((char *)data); - maxsize -= (zero + 1 - data); - data = zero + 1; + entry->value.u.stringval = dupstr(get_asciz(src)); break; case TYPE_FILENAME: - entry->value.u.fileval = - filename_deserialise(data, maxsize, &used); - if (!entry->value.u.fileval) { - if (subkeytypes[entry->key.primary] == TYPE_STR) - sfree(entry->key.secondary.s); - sfree(entry); - goto done; - } - data += used; - maxsize -= used; + entry->value.u.fileval = filename_deserialise(src); break; case TYPE_FONT: - entry->value.u.fontval = - fontspec_deserialise(data, maxsize, &used); - if (!entry->value.u.fontval) { - if (subkeytypes[entry->key.primary] == TYPE_STR) - sfree(entry->key.secondary.s); - sfree(entry); - goto done; - } - data += used; - maxsize -= used; + entry->value.u.fontval = fontspec_deserialise(src); break; } + + if (get_err(src)) { + free_entry(entry); + return false; + } + conf_insert(conf, entry); } - - done: - return (int)(data - start); } diff --git a/config.c b/config.c index 220c1aa8..f30a581f 100644 --- a/config.c +++ b/config.c @@ -15,7 +15,7 @@ #define HOST_BOX_TITLE "Host Name (or IP address)" #define PORT_BOX_TITLE "Port" -void conf_radiobutton_handler(union control *ctrl, void *dlg, +void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -43,11 +43,39 @@ void conf_radiobutton_handler(union control *ctrl, void *dlg, } } +void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + int button; + Conf *conf = (Conf *)data; + + /* + * Same as conf_radiobutton_handler, but using conf_set_bool in + * place of conf_set_int, because it's dealing with a bool-typed + * config option. + */ + if (event == EVENT_REFRESH) { + int val = conf_get_bool(conf, ctrl->radio.context.i); + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (val == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + conf_set_bool(conf, ctrl->radio.context.i, + ctrl->radio.buttondata[button].i); + } +} + #define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(union control *ctrl, void *dlg, +void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { - int key, invert; + int key; + bool invert; Conf *conf = (Conf *)data; /* @@ -57,9 +85,9 @@ void conf_checkbox_handler(union control *ctrl, void *dlg, key = ctrl->checkbox.context.i; if (key & CHECKBOX_INVERT) { key &= ~CHECKBOX_INVERT; - invert = 1; + invert = true; } else - invert = 0; + invert = false; /* * C lacks a logical XOR, so the following code uses the idiom @@ -68,14 +96,14 @@ void conf_checkbox_handler(union control *ctrl, void *dlg, */ if (event == EVENT_REFRESH) { - int val = conf_get_int(conf, key); + bool val = conf_get_bool(conf, key); dlg_checkbox_set(ctrl, dlg, (!val ^ !invert)); } else if (event == EVENT_VALCHANGE) { - conf_set_int(conf, key, !dlg_checkbox_get(ctrl,dlg) ^ !invert); + conf_set_bool(conf, key, !dlg_checkbox_get(ctrl,dlg) ^ !invert); } } -void conf_editbox_handler(union control *ctrl, void *dlg, +void conf_editbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { /* @@ -124,14 +152,15 @@ void conf_editbox_handler(union control *ctrl, void *dlg, } } -void conf_filesel_handler(union control *ctrl, void *dlg, +void conf_filesel_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { int key = ctrl->fileselect.context.i; Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { - dlg_filesel_set(ctrl, dlg, conf_get_filename(conf, key)); + dlg_filesel_set( + ctrl, dlg, conf_get_filename(conf, key)); } else if (event == EVENT_VALCHANGE) { Filename *filename = dlg_filesel_get(ctrl, dlg); conf_set_filename(conf, key, filename); @@ -139,14 +168,15 @@ void conf_filesel_handler(union control *ctrl, void *dlg, } } -void conf_fontsel_handler(union control *ctrl, void *dlg, +void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { int key = ctrl->fontselect.context.i; Conf *conf = (Conf *)data; if (event == EVENT_REFRESH) { - dlg_fontsel_set(ctrl, dlg, conf_get_fontspec(conf, key)); + dlg_fontsel_set( + ctrl, dlg, conf_get_fontspec(conf, key)); } else if (event == EVENT_VALCHANGE) { FontSpec *fontspec = dlg_fontsel_get(ctrl, dlg); conf_set_fontspec(conf, key, fontspec); @@ -154,7 +184,7 @@ void conf_fontsel_handler(union control *ctrl, void *dlg, } } -static void config_host_handler(union control *ctrl, void *dlg, +static void config_host_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -186,7 +216,7 @@ static void config_host_handler(union control *ctrl, void *dlg, } } -static void config_port_handler(union control *ctrl, void *dlg, +static void config_port_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -235,7 +265,7 @@ struct hostport { * routines can use it to conveniently identify the protocol radio * buttons in order to add to them. */ -void config_protocolbuttons_handler(union control *ctrl, void *dlg, +void config_protocolbuttons_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -267,10 +297,10 @@ void config_protocolbuttons_handler(union control *ctrl, void *dlg, conf_set_int(conf, CONF_protocol, newproto); if (oldproto != newproto) { - Backend *ob = backend_from_proto(oldproto); - Backend *nb = backend_from_proto(newproto); - assert(ob); - assert(nb); + const struct BackendVtable *ovt = backend_vt_from_proto(oldproto); + const struct BackendVtable *nvt = backend_vt_from_proto(newproto); + assert(ovt); + assert(nvt); /* Iff the user hasn't changed the port from the old protocol's * default, update it with the new protocol's default. * (This includes a "default" of 0, implying that there is no @@ -281,15 +311,15 @@ void config_protocolbuttons_handler(union control *ctrl, void *dlg, * getting to the protocol; we want that non-default port * to be preserved. */ port = conf_get_int(conf, CONF_port); - if (port == ob->default_port) - conf_set_int(conf, CONF_port, nb->default_port); + if (port == ovt->default_port) + conf_set_int(conf, CONF_port, nvt->default_port); } dlg_refresh(hp->host, dlg); dlg_refresh(hp->port, dlg); } } -static void loggingbuttons_handler(union control *ctrl, void *dlg, +static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -318,7 +348,7 @@ static void loggingbuttons_handler(union control *ctrl, void *dlg, } } -static void numeric_keypad_handler(union control *ctrl, void *dlg, +static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { int button; @@ -328,9 +358,9 @@ static void numeric_keypad_handler(union control *ctrl, void *dlg, * handler, but it has to handle two fields in Conf. */ if (event == EVENT_REFRESH) { - if (conf_get_int(conf, CONF_nethack_keypad)) + if (conf_get_bool(conf, CONF_nethack_keypad)) button = 2; - else if (conf_get_int(conf, CONF_app_keypad)) + else if (conf_get_bool(conf, CONF_app_keypad)) button = 1; else button = 0; @@ -340,16 +370,16 @@ static void numeric_keypad_handler(union control *ctrl, void *dlg, button = dlg_radiobutton_get(ctrl, dlg); assert(button >= 0 && button < ctrl->radio.nbuttons); if (button == 2) { - conf_set_int(conf, CONF_app_keypad, FALSE); - conf_set_int(conf, CONF_nethack_keypad, TRUE); + conf_set_bool(conf, CONF_app_keypad, false); + conf_set_bool(conf, CONF_nethack_keypad, true); } else { - conf_set_int(conf, CONF_app_keypad, (button != 0)); - conf_set_int(conf, CONF_nethack_keypad, FALSE); + conf_set_bool(conf, CONF_app_keypad, (button != 0)); + conf_set_bool(conf, CONF_nethack_keypad, false); } } } -static void cipherlist_handler(union control *ctrl, void *dlg, +static void cipherlist_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -395,7 +425,7 @@ static void cipherlist_handler(union control *ctrl, void *dlg, } #ifndef NO_GSSAPI -static void gsslist_handler(union control *ctrl, void *dlg, +static void gsslist_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -422,7 +452,7 @@ static void gsslist_handler(union control *ctrl, void *dlg, } #endif -static void kexlist_handler(union control *ctrl, void *dlg, +static void kexlist_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -442,7 +472,7 @@ static void kexlist_handler(union control *ctrl, void *dlg, /* (kexlist assumed to contain all algorithms) */ dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - for (i = 0; i < KEX_MAX; i++) { + for (i = 0; i < KEX_MAX; i++) { int k = conf_get_int_int(conf, CONF_ssh_kexlist, i); int j; const char *kstr = NULL; @@ -460,13 +490,13 @@ static void kexlist_handler(union control *ctrl, void *dlg, int i; /* Update array to match the list box. */ - for (i=0; i < KEX_MAX; i++) + for (i=0; i < KEX_MAX; i++) conf_set_int_int(conf, CONF_ssh_kexlist, i, dlg_listbox_getid(ctrl, dlg, i)); } } -static void hklist_handler(union control *ctrl, void *dlg, +static void hklist_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -509,7 +539,7 @@ static void hklist_handler(union control *ctrl, void *dlg, } } -static void printerbox_handler(union control *ctrl, void *dlg, +static void printerbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -545,7 +575,7 @@ static void printerbox_handler(union control *ctrl, void *dlg, } } -static void codepage_handler(union control *ctrl, void *dlg, +static void codepage_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -569,7 +599,7 @@ static void codepage_handler(union control *ctrl, void *dlg, } } -static void sshbug_handler(union control *ctrl, void *dlg, +static void sshbug_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -606,14 +636,14 @@ struct sessionsaver_data { union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; union control *okbutton, *cancelbutton; struct sesslist sesslist; - int midsession; + bool midsession; char *savedsession; /* the current contents of ssd->editbox */ }; static void sessionsaver_data_free(void *ssdv) { struct sessionsaver_data *ssd = (struct sessionsaver_data *)ssdv; - get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, false); sfree(ssd->savedsession); sfree(ssd); } @@ -623,14 +653,15 @@ static void sessionsaver_data_free(void *ssdv) * any, as this is done in more than one place below. Returns 0 for * failure. */ -static int load_selected_session(struct sessionsaver_data *ssd, - void *dlg, Conf *conf, int *maybe_launch) +static bool load_selected_session( + struct sessionsaver_data *ssd, + dlgparam *dlg, Conf *conf, bool *maybe_launch) { int i = dlg_listbox_index(ssd->listbox, dlg); - int isdef; + bool isdef; if (i < 0) { dlg_beep(dlg); - return 0; + return false; } isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); load_settings(ssd->sesslist.sessions[i], conf); @@ -642,10 +673,10 @@ static int load_selected_session(struct sessionsaver_data *ssd, /* Restore the selection, which might have been clobbered by * changing the value of the edit box. */ dlg_listbox_select(ssd->listbox, dlg, i); - return 1; + return true; } -static void sessionsaver_handler(union control *ctrl, void *dlg, +static void sessionsaver_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -685,7 +716,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg, dlg_listbox_select(ssd->listbox, dlg, top); } } else if (event == EVENT_ACTION) { - int mbl = FALSE; + bool mbl = false; if (!ssd->midsession && (ctrl == ssd->listbox || (ssd->loadbutton && ctrl == ssd->loadbutton))) { @@ -701,7 +732,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg, dlg_end(dlg, 1); /* it's all over, and succeeded */ } } else if (ctrl == ssd->savebutton) { - int isdef = !strcmp(ssd->savedsession, "Default Settings"); + bool isdef = !strcmp(ssd->savedsession, "Default Settings"); if (!ssd->savedsession[0]) { int i = dlg_listbox_index(ssd->listbox, dlg); if (i < 0) { @@ -720,8 +751,8 @@ static void sessionsaver_handler(union control *ctrl, void *dlg, sfree(errmsg); } } - get_sesslist(&ssd->sesslist, FALSE); - get_sesslist(&ssd->sesslist, TRUE); + get_sesslist(&ssd->sesslist, false); + get_sesslist(&ssd->sesslist, true); dlg_refresh(ssd->editbox, dlg); dlg_refresh(ssd->listbox, dlg); } else if (!ssd->midsession && @@ -731,8 +762,8 @@ static void sessionsaver_handler(union control *ctrl, void *dlg, dlg_beep(dlg); } else { del_settings(ssd->sesslist.sessions[i]); - get_sesslist(&ssd->sesslist, FALSE); - get_sesslist(&ssd->sesslist, TRUE); + get_sesslist(&ssd->sesslist, false); + get_sesslist(&ssd->sesslist, true); dlg_refresh(ssd->listbox, dlg); } } else if (ctrl == ssd->okbutton) { @@ -751,7 +782,7 @@ static void sessionsaver_handler(union control *ctrl, void *dlg, if (dlg_last_focused(ctrl, dlg) == ssd->listbox && !conf_launchable(conf)) { Conf *conf2 = conf_new(); - int mbl = FALSE; + bool mbl = false; if (!load_selected_session(ssd, dlg, conf2, &mbl)) { dlg_beep(dlg); conf_free(conf2); @@ -786,7 +817,7 @@ struct charclass_data { union control *listbox, *editbox, *button; }; -static void charclass_handler(union control *ctrl, void *dlg, +static void charclass_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -841,13 +872,14 @@ static const char *const colours[] = { "ANSI White", "ANSI White Bold" }; -static void colour_handler(union control *ctrl, void *dlg, +static void colour_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; struct colour_data *cd = (struct colour_data *)ctrl->generic.context.p; - int update = FALSE, clear = FALSE, r, g, b; + bool update = false, clear = false; + int r, g, b; if (event == EVENT_REFRESH) { if (ctrl == cd->listbox) { @@ -857,22 +889,22 @@ static void colour_handler(union control *ctrl, void *dlg, for (i = 0; i < lenof(colours); i++) dlg_listbox_add(ctrl, dlg, colours[i]); dlg_update_done(ctrl, dlg); - clear = TRUE; - update = TRUE; + clear = true; + update = true; } } else if (event == EVENT_SELCHANGE) { if (ctrl == cd->listbox) { /* The user has selected a colour. Update the RGB text. */ int i = dlg_listbox_index(ctrl, dlg); if (i < 0) { - clear = TRUE; + clear = true; } else { - clear = FALSE; + clear = false; r = conf_get_int_int(conf, CONF_colours, i*3+0); g = conf_get_int_int(conf, CONF_colours, i*3+1); b = conf_get_int_int(conf, CONF_colours, i*3+2); } - update = TRUE; + update = true; } } else if (event == EVENT_VALCHANGE) { if (ctrl == cd->redit || ctrl == cd->gedit || ctrl == cd->bedit) { @@ -925,8 +957,8 @@ static void colour_handler(union control *ctrl, void *dlg, conf_set_int_int(conf, CONF_colours, i*3+0, r); conf_set_int_int(conf, CONF_colours, i*3+1, g); conf_set_int_int(conf, CONF_colours, i*3+2, b); - clear = FALSE; - update = TRUE; + clear = false; + update = true; } } } @@ -949,7 +981,7 @@ struct ttymodes_data { union control *valradio, *valbox, *setbutton, *listbox; }; -static void ttymodes_handler(union control *ctrl, void *dlg, +static void ttymodes_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1005,7 +1037,7 @@ static void ttymodes_handler(union control *ctrl, void *dlg, char type; { - const char *types = "ANV"; + const char types[] = {'A', 'N', 'V'}; int button = dlg_radiobutton_get(td->valradio, dlg); assert(button >= 0 && button < lenof(types)); type = types[button]; @@ -1034,7 +1066,7 @@ struct environ_data { union control *varbox, *valbox, *addbutton, *rembutton, *listbox; }; -static void environ_handler(union control *ctrl, void *dlg, +static void environ_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1110,7 +1142,7 @@ struct portfwd_data { #endif }; -static void portfwd_handler(union control *ctrl, void *dlg, +static void portfwd_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1227,7 +1259,7 @@ static void portfwd_handler(union control *ctrl, void *dlg, if (key) { static const char *const afs = "A46"; static const char *const dirs = "LRD"; - char *afp; + const char *afp; int dir; #ifndef NO_IPV6 int idx; @@ -1274,7 +1306,7 @@ struct manual_hostkey_data { union control *addbutton, *rembutton, *listbox, *keybox; }; -static void manual_hostkey_handler(union control *ctrl, void *dlg, +static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { Conf *conf = (Conf *)data; @@ -1337,7 +1369,106 @@ static void manual_hostkey_handler(union control *ctrl, void *dlg, } } -void setup_config_box(struct controlbox *b, int midsession, +static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + int setting = ctrl->generic.context.i; +#ifdef NAMED_CLIPBOARDS + int strsetting = ctrl->editbox.context2.i; +#endif + + static const struct { + const char *name; + int id; + } options[] = { + {"No action", CLIPUI_NONE}, + {CLIPNAME_IMPLICIT, CLIPUI_IMPLICIT}, + {CLIPNAME_EXPLICIT, CLIPUI_EXPLICIT}, + }; + + if (event == EVENT_REFRESH) { + int i, val = conf_get_int(conf, setting); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + +#ifdef NAMED_CLIPBOARDS + for (i = 0; i < lenof(options); i++) + dlg_listbox_add(ctrl, dlg, options[i].name); + if (val == CLIPUI_CUSTOM) { + const char *sval = conf_get_str(conf, strsetting); + for (i = 0; i < lenof(options); i++) + if (!strcmp(sval, options[i].name)) + break; /* needs escaping */ + if (i < lenof(options) || sval[0] == '=') { + char *escaped = dupcat("=", sval, (const char *)NULL); + dlg_editbox_set(ctrl, dlg, escaped); + sfree(escaped); + } else { + dlg_editbox_set(ctrl, dlg, sval); + } + } else { + dlg_editbox_set(ctrl, dlg, options[0].name); /* fallback */ + for (i = 0; i < lenof(options); i++) + if (val == options[i].id) + dlg_editbox_set(ctrl, dlg, options[i].name); + } +#else + for (i = 0; i < lenof(options); i++) + dlg_listbox_addwithid(ctrl, dlg, options[i].name, options[i].id); + dlg_listbox_select(ctrl, dlg, 0); /* fallback */ + for (i = 0; i < lenof(options); i++) + if (val == options[i].id) + dlg_listbox_select(ctrl, dlg, i); +#endif + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE +#ifdef NAMED_CLIPBOARDS + || event == EVENT_VALCHANGE +#endif + ) { +#ifdef NAMED_CLIPBOARDS + const char *sval = dlg_editbox_get(ctrl, dlg); + int i; + + for (i = 0; i < lenof(options); i++) + if (!strcmp(sval, options[i].name)) { + conf_set_int(conf, setting, options[i].id); + conf_set_str(conf, strsetting, ""); + break; + } + if (i == lenof(options)) { + conf_set_int(conf, setting, CLIPUI_CUSTOM); + if (sval[0] == '=') + sval++; + conf_set_str(conf, strsetting, sval); + } +#else + int index = dlg_listbox_index(ctrl, dlg); + if (index >= 0) { + int val = dlg_listbox_getid(ctrl, dlg, index); + conf_set_int(conf, setting, val); + } +#endif + } +} + +static void clipboard_control(struct controlset *s, const char *label, + char shortcut, int percentage, intorptr helpctx, + int setting, int strsetting) +{ +#ifdef NAMED_CLIPBOARDS + ctrl_combobox(s, label, shortcut, percentage, helpctx, + clipboard_selector_handler, I(setting), I(strsetting)); +#else + /* strsetting isn't needed in this case */ + ctrl_droplist(s, label, shortcut, percentage, helpctx, + clipboard_selector_handler, I(setting)); +#endif +} + +void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo) { struct controlset *s; @@ -1369,11 +1500,11 @@ void setup_config_box(struct controlbox *b, int midsession, (char)(midsession ? 'a' : 'o'), HELPCTX(no_help), sessionsaver_handler, P(ssd)); - ssd->okbutton->button.isdefault = TRUE; + ssd->okbutton->button.isdefault = true; ssd->okbutton->generic.column = 3; ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help), sessionsaver_handler, P(ssd)); - ssd->cancelbutton->button.iscancel = TRUE; + ssd->cancelbutton->button.iscancel = true; ssd->cancelbutton->generic.column = 4; /* We carefully don't close the 5-column part, so that platform- * specific add-ons can put extra buttons alongside Open and Cancel. */ @@ -1404,7 +1535,7 @@ void setup_config_box(struct controlbox *b, int midsession, hp->port = c; ctrl_columns(s, 1, 100); - if (!backend_from_proto(PROT_SSH)) { + if (!backend_vt_from_proto(PROT_SSH)) { ctrl_radiobuttons(s, "Connection type:", NO_SHORTCUT, 3, HELPCTX(session_hostname), config_protocolbuttons_handler, P(hp), @@ -1431,7 +1562,7 @@ void setup_config_box(struct controlbox *b, int midsession, midsession ? "Save the current session settings" : "Load, save or delete a stored session"); ctrl_columns(s, 2, 75, 25); - get_sesslist(&ssd->sesslist, TRUE); + get_sesslist(&ssd->sesslist, true); ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100, HELPCTX(session_saved), sessionsaver_handler, P(ssd), P(NULL)); @@ -1495,7 +1626,7 @@ void setup_config_box(struct controlbox *b, int midsession, { const char *sshlogname, *sshrawlogname; if ((midsession && protocol == PROT_SSH) || - (!midsession && backend_from_proto(PROT_SSH))) { + (!midsession && backend_vt_from_proto(PROT_SSH))) { sshlogname = "SSH packets"; sshrawlogname = "SSH packets and raw data"; } else { @@ -1514,7 +1645,7 @@ void setup_config_box(struct controlbox *b, int midsession, NULL); } ctrl_filesel(s, "Log file name:", 'f', - NULL, TRUE, "Select session log file name", + NULL, true, "Select session log file name", HELPCTX(logging_filename), conf_filesel_handler, I(CONF_logfilename)); ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," @@ -1529,9 +1660,12 @@ void setup_config_box(struct controlbox *b, int midsession, ctrl_checkbox(s, "Flush log file frequently", 'u', HELPCTX(logging_flush), conf_checkbox_handler, I(CONF_logflush)); + ctrl_checkbox(s, "Include header", 'i', + HELPCTX(logging_header), + conf_checkbox_handler, I(CONF_logheader)); if ((midsession && protocol == PROT_SSH) || - (!midsession && backend_from_proto(PROT_SSH))) { + (!midsession && backend_vt_from_proto(PROT_SSH))) { s = ctrl_getset(b, "Session/Logging", "ssh", "Options specific to SSH packet logging"); ctrl_checkbox(s, "Omit known password fields", 'k', @@ -1599,14 +1733,14 @@ void setup_config_box(struct controlbox *b, int midsession, "Change the sequences sent by:"); ctrl_radiobuttons(s, "The Backspace key", 'b', 2, HELPCTX(keyboard_backspace), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_bksp_is_delete), "Control-H", I(0), "Control-? (127)", I(1), NULL); ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, HELPCTX(keyboard_homeend), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_rxvt_homeend), - "Standard", I(0), "rxvt", I(1), NULL); + "Standard", I(false), "rxvt", I(true), NULL); ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3, HELPCTX(keyboard_funkeys), conf_radiobutton_handler, @@ -1618,7 +1752,7 @@ void setup_config_box(struct controlbox *b, int midsession, "Application keypad settings:"); ctrl_radiobuttons(s, "Initial state of cursor keys:", 'r', 3, HELPCTX(keyboard_appcursor), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_app_cursor), "Normal", I(0), "Application", I(1), NULL); ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3, @@ -1840,6 +1974,9 @@ void setup_config_box(struct controlbox *b, int midsession, ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', HELPCTX(selection_linedraw), conf_checkbox_handler, I(CONF_rawcnp)); + ctrl_checkbox(s, "Enable VT100 line drawing even in UTF-8 mode",'8', + HELPCTX(translation_utf8linedraw), + conf_checkbox_handler, I(CONF_utf8linedraw)); /* * The Window/Selection panel. @@ -1855,17 +1992,45 @@ void setup_config_box(struct controlbox *b, int midsession, "Default selection mode (Alt+drag does the other one):", NO_SHORTCUT, 2, HELPCTX(selection_rect), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_rect_select), - "Normal", 'n', I(0), - "Rectangular block", 'r', I(1), NULL); + "Normal", 'n', I(false), + "Rectangular block", 'r', I(true), NULL); + + s = ctrl_getset(b, "Window/Selection", "clipboards", + "Assign copy/paste actions to clipboards"); + ctrl_checkbox(s, "Auto-copy selected text to " + CLIPNAME_EXPLICIT_OBJECT, + NO_SHORTCUT, HELPCTX(selection_autocopy), + conf_checkbox_handler, I(CONF_mouseautocopy)); + clipboard_control(s, "Mouse paste action:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_mousepaste, CONF_mousepaste_custom); + clipboard_control(s, "{Ctrl,Shift} + Ins:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + clipboard_control(s, "Ctrl + Shift + {C,V}:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); + + s = ctrl_getset(b, "Window/Selection", "paste", + "Control pasting of text from clipboard to terminal"); + ctrl_checkbox(s, "Permit control characters in pasted text", + NO_SHORTCUT, HELPCTX(selection_pastectrl), + conf_checkbox_handler, I(CONF_paste_controls)); - s = ctrl_getset(b, "Window/Selection", "charclass", - "Control the select-one-word-at-a-time mode"); + /* + * The Window/Selection/Copy panel. + */ + ctrl_settitle(b, "Window/Selection/Copy", + "Options controlling copying from terminal to clipboard"); + + s = ctrl_getset(b, "Window/Selection/Copy", "charclass", + "Classes of character that group together"); ccd = (struct charclass_data *) ctrl_alloc(b, sizeof(struct charclass_data)); ccd->listbox = ctrl_listbox(s, "Character classes:", 'e', - HELPCTX(selection_charclasses), + HELPCTX(copy_charclasses), charclass_handler, P(ccd)); ccd->listbox->listbox.multisel = 1; ccd->listbox->listbox.ncols = 4; @@ -1876,11 +2041,11 @@ void setup_config_box(struct controlbox *b, int midsession, ccd->listbox->listbox.percentages[3] = 40; ctrl_columns(s, 2, 67, 33); ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50, - HELPCTX(selection_charclasses), + HELPCTX(copy_charclasses), charclass_handler, P(ccd), P(NULL)); ccd->editbox->generic.column = 0; ccd->button = ctrl_pushbutton(s, "Set", 's', - HELPCTX(selection_charclasses), + HELPCTX(copy_charclasses), charclass_handler, P(ccd)); ccd->button->generic.column = 1; ctrl_columns(s, 1, 100); @@ -1898,6 +2063,9 @@ void setup_config_box(struct controlbox *b, int midsession, ctrl_checkbox(s, "Allow terminal to use xterm 256-colour mode", '2', HELPCTX(colours_xterm256), conf_checkbox_handler, I(CONF_xterm_256_colour)); + ctrl_checkbox(s, "Allow terminal to use 24-bit colours", '4', + HELPCTX(colours_truecolour), conf_checkbox_handler, + I(CONF_true_colour)); ctrl_radiobuttons(s, "Indicate bolded text by changing:", 'b', 3, HELPCTX(colours_bold), conf_radiobutton_handler, I(CONF_bold_style), @@ -1974,7 +2142,7 @@ void setup_config_box(struct controlbox *b, int midsession, #endif { - const char *label = backend_from_proto(PROT_SSH) ? + const char *label = backend_vt_from_proto(PROT_SSH) ? "Logical name of remote host (e.g. for SSH key lookup):" : "Logical name of remote host:"; s = ctrl_getset(b, "Connection", "identity", @@ -2006,10 +2174,10 @@ void setup_config_box(struct controlbox *b, int midsession, sfree(user); ctrl_radiobuttons(s, "When username is not specified:", 'n', 4, HELPCTX(connection_username_from_env), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_username_from_env), - "Prompt", I(FALSE), - userlabel, I(TRUE), + "Prompt", I(false), + userlabel, I(true), NULL); sfree(userlabel); } @@ -2111,7 +2279,7 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(proxy_auth), conf_editbox_handler, I(CONF_proxy_password), I(1)); - c->editbox.password = 1; + c->editbox.password = true; ctrl_editbox(s, "Telnet command", 'm', 100, HELPCTX(proxy_command), conf_editbox_handler, @@ -2145,15 +2313,15 @@ void setup_config_box(struct controlbox *b, int midsession, ctrl_radiobuttons(s, "Handling of OLD_ENVIRON ambiguity:", NO_SHORTCUT, 2, HELPCTX(telnet_oldenviron), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_rfc_environ), - "BSD (commonplace)", 'b', I(0), - "RFC 1408 (unusual)", 'f', I(1), NULL); + "BSD (commonplace)", 'b', I(false), + "RFC 1408 (unusual)", 'f', I(true), NULL); ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, HELPCTX(telnet_passive), - conf_radiobutton_handler, + conf_radiobutton_bool_handler, I(CONF_passive_telnet), - "Passive", I(1), "Active", I(0), NULL); + "Passive", I(true), "Active", I(false), NULL); } ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', HELPCTX(telnet_specialkeys), @@ -2186,7 +2354,8 @@ void setup_config_box(struct controlbox *b, int midsession, * when we're not doing SSH. */ - if (backend_from_proto(PROT_SSH) && (!midsession || protocol == PROT_SSH)) { + if (backend_vt_from_proto(PROT_SSH) && + (!midsession || protocol == PROT_SSH)) { /* * The Connection/SSH panel. @@ -2272,7 +2441,11 @@ void setup_config_box(struct controlbox *b, int midsession, c = ctrl_draglist(s, "Algorithm selection policy:", 's', HELPCTX(ssh_kexlist), kexlist_handler, P(NULL)); - c->listbox.height = 5; + c->listbox.height = KEX_MAX; + ctrl_checkbox(s, "Attempt GSSAPI key exchange", + 'k', HELPCTX(ssh_gssapi), + conf_checkbox_handler, + I(CONF_try_gssapi_kex)); s = ctrl_getset(b, "Connection/SSH/Kex", "repeat", "Options controlling key re-exchange"); @@ -2282,6 +2455,11 @@ void setup_config_box(struct controlbox *b, int midsession, conf_editbox_handler, I(CONF_ssh_rekey_time), I(-1)); + 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)); ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20, HELPCTX(ssh_kex_repeat), conf_editbox_handler, @@ -2327,7 +2505,7 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); mh->rembutton->generic.column = 1; - mh->rembutton->generic.tabdelay = 1; + mh->rembutton->generic.tabdelay = true; mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_manual_hostkeys), manual_hostkey_handler, P(mh)); @@ -2336,7 +2514,7 @@ void setup_config_box(struct controlbox *b, int midsession, * it become really unhelpful if a horizontal scrollbar * appears, so we suppress that. */ mh->listbox->listbox.height = 2; - mh->listbox->listbox.hscroll = FALSE; + mh->listbox->listbox.hscroll = false; ctrl_tabdelay(s, mh->rembutton); mh->keybox = ctrl_editbox(s, "Key", 'k', 80, HELPCTX(ssh_kex_manual_hostkeys), @@ -2412,7 +2590,7 @@ void setup_config_box(struct controlbox *b, int midsession, conf_checkbox_handler, I(CONF_change_username)); ctrl_filesel(s, "Private key file for authentication:", 'k', - FILTER_KEY_FILES, FALSE, "Select private key file", + FILTER_KEY_FILES, false, "Select private key file", HELPCTX(ssh_auth_privkey), conf_filesel_handler, I(CONF_keyfile)); @@ -2430,6 +2608,11 @@ void setup_config_box(struct controlbox *b, int midsession, conf_checkbox_handler, I(CONF_try_gssapi_auth)); + ctrl_checkbox(s, "Attempt GSSAPI key exchange (SSH-2 only)", + 'k', HELPCTX(ssh_gssapi), + conf_checkbox_handler, + I(CONF_try_gssapi_kex)); + ctrl_checkbox(s, "Allow GSSAPI credential delegation", 'l', HELPCTX(ssh_gssapi_delegation), conf_checkbox_handler, @@ -2464,7 +2647,7 @@ void setup_config_box(struct controlbox *b, int midsession, */ ctrl_filesel(s, "User-supplied GSSAPI library path:", 's', - FILTER_DYNLIB_FILES, FALSE, "Select library file", + FILTER_DYNLIB_FILES, false, "Select library file", HELPCTX(ssh_gssapi_libraries), conf_filesel_handler, I(CONF_ssh_gss_custom)); @@ -2504,7 +2687,7 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(ssh_ttymodes), ttymodes_handler, P(td)); td->setbutton->generic.column = 1; - td->setbutton->generic.tabdelay = 1; + td->setbutton->generic.tabdelay = 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. */ @@ -2573,7 +2756,7 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); pfd->rembutton->generic.column = 2; - pfd->rembutton->generic.tabdelay = 1; + pfd->rembutton->generic.tabdelay = true; pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); @@ -2590,7 +2773,7 @@ void setup_config_box(struct controlbox *b, int midsession, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd)); pfd->addbutton->generic.column = 2; - pfd->addbutton->generic.tabdelay = 1; + pfd->addbutton->generic.tabdelay = true; pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40, HELPCTX(ssh_tunnels_portfwd), portfwd_handler, P(pfd), P(NULL)); diff --git a/configure.ac b/configure.ac index adb31915..a09d6417 100644 --- a/configure.ac +++ b/configure.ac @@ -120,7 +120,10 @@ case "$gtk_version_desired:$gtk" in # manual check for gtk1 AC_PATH_PROG(GTK1_CONFIG, gtk-config, absent) if test "$GTK1_CONFIG" != "absent"; then - GTK_CFLAGS=`"$GTK1_CONFIG" --cflags` + # the gtk1 headers need -std=gnu89, which flips round the + # definitions of 'inline' and 'extern inline' to their old GNU + # semantics before C99 chose different ones + GTK_CFLAGS="`"$GTK1_CONFIG" --cflags` -std=gnu89" GTK_LIBS=`"$GTK1_CONFIG" --libs` gtk=1 fi @@ -157,7 +160,7 @@ AC_CHECK_LIB(X11, XOpenDisplay, [GTK_LIBS="-lX11 $GTK_LIBS" AC_DEFINE([HAVE_LIBX11],[],[Define if libX11.a is available])]) -AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx]) +AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx fstatat dirfd]) AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[#include ]]) AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME],[],[Define if clock_gettime() is available])]) @@ -185,7 +188,7 @@ AS_IF([test AS_VAR_GET(x_cv_linux_so_peercred) = yes], if test "x$GCC" = "xyes"; then : - AC_SUBST(WARNINGOPTS, ['-Wall -Werror']) + AC_SUBST(WARNINGOPTS, ['-Wall -Werror -Wpointer-arith']) else : AC_SUBST(WARNINGOPTS, []) diff --git a/contrib/cygtermd/README b/contrib/cygtermd/README index ebfdfdd7..a722fe10 100644 --- a/contrib/cygtermd/README +++ b/contrib/cygtermd/README @@ -8,4 +8,4 @@ install it in Cygwin's /bin, and configure PuTTY to use it as a local proxy process. For detailed instructions, see the PuTTY Wishlist page at -http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html +https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html diff --git a/contrib/cygtermd/main.c b/contrib/cygtermd/main.c index 84e6c75e..154c4315 100644 --- a/contrib/cygtermd/main.c +++ b/contrib/cygtermd/main.c @@ -24,7 +24,7 @@ sel *asel; sel_rfd *netr, *ptyr, *sigr; int ptyfd; sel_wfd *netw, *ptyw; -Telnet telnet; +Telnet *telnet; #define BUF 65536 @@ -36,7 +36,7 @@ void sigchld(int signum) void fatal(const char *fmt, ...) { va_list ap; - fprintf(stderr, "FIXME: "); + fprintf(stderr, "cygtermd: "); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); diff --git a/contrib/cygtermd/pty.h b/contrib/cygtermd/pty.h index bee10e47..261fc41d 100644 --- a/contrib/cygtermd/pty.h +++ b/contrib/cygtermd/pty.h @@ -1,9 +1,9 @@ /* - * pty.h - FIXME + * pty.h - declare functions for pty setup */ -#ifndef FIXME_PTY_H -#define FIXME_PTY_H +#ifndef CYGTERMD_PTY_H +#define CYGTERMD_PTY_H #include "telnet.h" /* for struct shdata */ @@ -25,4 +25,4 @@ void pty_resize(int w, int h); int run_program_in_pty(const struct shell_data *shdata, char *directory, char **program_args); -#endif /* FIXME_PTY_H */ +#endif /* CYGTERMD_PTY_H */ diff --git a/contrib/cygtermd/sel.h b/contrib/cygtermd/sel.h index 98767e2f..42c615ff 100644 --- a/contrib/cygtermd/sel.h +++ b/contrib/cygtermd/sel.h @@ -4,8 +4,8 @@ * reads. */ -#ifndef FIXME_SEL_H -#define FIXME_SEL_H +#ifndef CYGTERMD_SEL_H +#define CYGTERMD_SEL_H typedef struct sel sel; typedef struct sel_wfd sel_wfd; @@ -158,4 +158,4 @@ void sel_wfd_setfd(sel_wfd *wfd, int fd); */ void sel_rfd_setfd(sel_rfd *rfd, int fd); -#endif /* FIXME_SEL_H */ +#endif /* CYGTERMD_SEL_H */ diff --git a/contrib/cygtermd/telnet.c b/contrib/cygtermd/telnet.c index 08487374..6c96eed5 100644 --- a/contrib/cygtermd/telnet.c +++ b/contrib/cygtermd/telnet.c @@ -5,6 +5,7 @@ #include #include +#include #include #include "sel.h" @@ -12,13 +13,6 @@ #include "malloc.h" #include "pty.h" -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - #define IAC 255 /* interpret as command: */ #define DONT 254 /* you are not to use option */ #define DO 253 /* please, you use option */ @@ -172,7 +166,7 @@ static const struct Opt *const opts[] = { &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL }; -struct telnet_tag { +struct Telnet { int opt_states[NUM_OPTS]; int sb_opt, sb_len; @@ -203,17 +197,17 @@ struct telnet_tag { #define SB_DELTA 1024 -static void send_opt(Telnet telnet, int cmd, int option) +static void send_opt(Telnet *telnet, int cmd, int option) { unsigned char b[3]; b[0] = IAC; b[1] = cmd; b[2] = option; - sel_write(telnet->net, (char *)b, 3); + sel_write(telnet->net, b, 3); } -static void deactivate_option(Telnet telnet, const struct Opt *o) +static void deactivate_option(Telnet *telnet, const struct Opt *o) { if (telnet->opt_states[o->index] == REQUESTED || telnet->opt_states[o->index] == ACTIVE) @@ -224,11 +218,11 @@ static void deactivate_option(Telnet telnet, const struct Opt *o) /* * Generate side effects of enabling or disabling an option. */ -static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) +static void option_side_effects(Telnet *telnet, const struct Opt *o, int enabled) { } -static void activate_option(Telnet telnet, const struct Opt *o) +static void activate_option(Telnet *telnet, const struct Opt *o) { if (o->option == TELOPT_NEW_ENVIRON || o->option == TELOPT_OLD_ENVIRON || @@ -245,7 +239,7 @@ static void activate_option(Telnet telnet, const struct Opt *o) option_side_effects(telnet, o, 1); } -static void done_option(Telnet telnet, int option) +static void done_option(Telnet *telnet, int option) { if (option == TELOPT_OLD_ENVIRON) telnet->old_environ_done = 1; @@ -260,7 +254,7 @@ static void done_option(Telnet telnet, int option) } } -static void refused_option(Telnet telnet, const struct Opt *o) +static void refused_option(Telnet *telnet, const struct Opt *o) { done_option(telnet, o->option); if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && @@ -272,7 +266,7 @@ static void refused_option(Telnet telnet, const struct Opt *o) option_side_effects(telnet, o, 0); } -static void proc_rec_opt(Telnet telnet, int cmd, int option) +static void proc_rec_opt(Telnet *telnet, int cmd, int option) { const struct Opt *const *o; @@ -323,7 +317,7 @@ static void proc_rec_opt(Telnet telnet, int cmd, int option) send_opt(telnet, (cmd == WILL ? DONT : WONT), option); } -static void process_subneg(Telnet telnet) +static void process_subneg(Telnet *telnet) { int var, value, n; @@ -400,7 +394,7 @@ static void process_subneg(Telnet telnet) } } -void telnet_from_net(Telnet telnet, char *buf, int len) +void telnet_from_net(Telnet *telnet, char *buf, int len) { while (len--) { int c = (unsigned char) *buf++; @@ -497,11 +491,11 @@ void telnet_from_net(Telnet telnet, char *buf, int len) } } -Telnet telnet_new(sel_wfd *net, sel_wfd *pty) +Telnet *telnet_new(sel_wfd *net, sel_wfd *pty) { - Telnet telnet; + Telnet *telnet; - telnet = snew(struct telnet_tag); + telnet = snew(Telnet); telnet->sb_buf = NULL; telnet->sb_size = 0; telnet->state = TOP_LEVEL; @@ -532,13 +526,13 @@ Telnet telnet_new(sel_wfd *net, sel_wfd *pty) return telnet; } -void telnet_free(Telnet telnet) +void telnet_free(Telnet *telnet) { sfree(telnet->sb_buf); sfree(telnet); } -void telnet_from_pty(Telnet telnet, char *buf, int len) +void telnet_from_pty(Telnet *telnet, char *buf, int len) { unsigned char *p, *end; static const unsigned char iac[2] = { IAC, IAC }; @@ -554,16 +548,16 @@ void telnet_from_pty(Telnet telnet, char *buf, int len) while (p < end && iswritable(*p)) p++; - sel_write(telnet->net, (char *)q, p - q); + sel_write(telnet->net, q, p - q); while (p < end && !iswritable(*p)) { - sel_write(telnet->net, (char *)(*p == IAC ? iac : cr), 2); + sel_write(telnet->net, *p == IAC ? iac : cr, 2); p++; } } } -int telnet_shell_ok(Telnet telnet, struct shell_data *shdata) +int telnet_shell_ok(Telnet *telnet, struct shell_data *shdata) { if (telnet->shell_ok) *shdata = telnet->shdata; /* structure copy */ diff --git a/contrib/cygtermd/telnet.h b/contrib/cygtermd/telnet.h index 1a74cab1..2dc1582b 100644 --- a/contrib/cygtermd/telnet.h +++ b/contrib/cygtermd/telnet.h @@ -2,12 +2,12 @@ * Header declaring Telnet-handling functions. */ -#ifndef FIXME_TELNET_H -#define FIXME_TELNET_H +#ifndef CYGTERMD_TELNET_H +#define CYGTERMD_TELNET_H #include "sel.h" -typedef struct telnet_tag *Telnet; +typedef struct Telnet Telnet; struct shell_data { char **envvars; /* array of "VAR=value" terms */ @@ -18,24 +18,24 @@ struct shell_data { /* * Create and destroy a Telnet structure. */ -Telnet telnet_new(sel_wfd *net, sel_wfd *pty); -void telnet_free(Telnet telnet); +Telnet *telnet_new(sel_wfd *net, sel_wfd *pty); +void telnet_free(Telnet *telnet); /* * Process data read from the pty. */ -void telnet_from_pty(Telnet telnet, char *buf, int len); +void telnet_from_pty(Telnet *telnet, char *buf, int len); /* * Process Telnet protocol data received from the network. */ -void telnet_from_net(Telnet telnet, char *buf, int len); +void telnet_from_net(Telnet *telnet, char *buf, int len); /* * Return true if pre-shell-startup negotiations are complete and * it's safe to start the shell subprocess now. On a true return, * also fills in the shell_data structure. */ -int telnet_shell_ok(Telnet telnet, struct shell_data *shdata); +int telnet_shell_ok(Telnet *telnet, struct shell_data *shdata); -#endif /* FIXME_TELNET_H */ +#endif /* CYGTERMD_TELNET_H */ diff --git a/contrib/gdb.py b/contrib/gdb.py new file mode 100644 index 00000000..a993cc89 --- /dev/null +++ b/contrib/gdb.py @@ -0,0 +1,96 @@ +import gdb +import re +import gdb.printing + +class PuTTYBignumPrettyPrinter(gdb.printing.PrettyPrinter): + "Pretty-print PuTTY's Bignum type." + name = "Bignum" + + def __init__(self, val): + super(PuTTYBignumPrettyPrinter, self).__init__(self.name) + self.val = val + + def to_string(self): + type_BignumInt = gdb.lookup_type("BignumInt") + type_BignumIntPtr = type_BignumInt.pointer() + BIGNUM_INT_BITS = 8 * type_BignumInt.sizeof + array = self.val.cast(type_BignumIntPtr) + aget = lambda i: int(array[i]) & ((1 << BIGNUM_INT_BITS)-1) + + try: + length = aget(0) + value = 0 + for i in range(length): + value |= aget(i+1) << (BIGNUM_INT_BITS * i) + return "Bignum({:#x})".format(value) + + except gdb.MemoryError: + address = int(array) + if address == 0: + return "Bignum(NULL)".format(address) + return "Bignum(invalid @ {:#x})".format(address) + +rcpp = gdb.printing.RegexpCollectionPrettyPrinter("PuTTY") +rcpp.add_printer(PuTTYBignumPrettyPrinter.name, "^Bignum$", + PuTTYBignumPrettyPrinter) + +gdb.printing.register_pretty_printer(None, rcpp) + +class MemDumpCommand(gdb.Command): + """Print a hex+ASCII dump of object EXP. + +EXP must be an expression whose value resides in memory. The +contents of the memory it occupies are printed in a standard hex +dump format, with each line showing an offset relative to the +address of EXP, then the hex byte values of the memory at that +offset, and then a translation into ASCII of the same bytes (with +values outside the printable ASCII range translated as '.'). + +To dump a number of bytes from a particular address, it's useful +to use the gdb expression extensions {TYPE} and @LENGTH. For +example, if 'ptr' and 'len' are variables giving an address and a +length in bytes, then the command + + memdump {char} ptr @ len + +will dump the range of memory described by those two variables.""" + + def __init__(self): + super(MemDumpCommand, self).__init__( + "memdump", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) + + def invoke(self, cmdline, from_tty): + expr = gdb.parse_and_eval(cmdline) + try: + start, size = int(expr.address), expr.type.sizeof + except gdb.error as e: + sys.stderr.write(str(e)) + return + except (TypeError, AttributeError): + sys.stderr.write("expression must identify an object in memory") + return + + width = 16 + line_ptr_type = gdb.lookup_type( + "unsigned char").const().array(width).pointer() + + dumpaddr = 0 + while size > 0: + line = gdb.Value(start).cast(line_ptr_type).dereference() + thislinelen = min(size, width) + start += thislinelen + size -= thislinelen + + dumpline = [None, " "] + [" "] * width + [" "] + [""] * width + + dumpline[0] = "{:08x}".format(dumpaddr) + dumpaddr += thislinelen + + for i in range(thislinelen): + ch = int(line[i]) & 0xFF + dumpline[2+i] = " {:02x}".format(ch) + dumpline[3+width+i] = chr(ch) if 0x20 <= ch < 0x7F else "." + + sys.stdout.write("".join(dumpline) + "\n") + +MemDumpCommand() diff --git a/contrib/logparse.pl b/contrib/logparse.pl index 4805c0f4..fe5fc53e 100755 --- a/contrib/logparse.pl +++ b/contrib/logparse.pl @@ -1,22 +1,34 @@ #!/usr/bin/perl +use Getopt::Long; use strict; use warnings; use FileHandle; my $dumpchannels = 0; my $dumpdata = 0; -while ($ARGV[0] =~ /^-/) { - my $opt = shift @ARGV; - if ($opt eq "--") { - last; # stop processing options - } elsif ($opt eq "-c") { - $dumpchannels = 1; - } elsif ($opt eq "-d") { - $dumpdata = 1; - } else { - die "unrecognised option '$opt'\n"; - } +my $pass_through_events = 0; +my $verbose_all; +my %verbose_packet; +GetOptions("dump-channels|c" => \$dumpchannels, + "dump-data|d" => \$dumpdata, + "verbose|v" => \$verbose_all, + "full|f=s" => sub { $verbose_packet{$_[1]} = 1; }, + "events|e" => \$pass_through_events, + "help" => sub { &usage(\*STDOUT, 0); }) + or &usage(\*STDERR, 1); + +sub usage { + my ($fh, $exitstatus) = @_; + print $fh <<'EOF'; +usage: logparse.pl [ options ] [ input-log-file ] +options: --dump-channels, -c dump the final state of every channel + --dump-data, -d save data of every channel to ch0.i, ch0.o, ... + --full=PKT, -f PKT print extra detail for packets of type PKT + --verbose, -v print extra detail for all packets if available + --events, -e copy Event Log messages from input log file +EOF + exit $exitstatus; } my @channels = (); # ultimate channel ids are indices in this array @@ -100,6 +112,41 @@ my ($direction, $seq, $data) = @_; print "\n"; }, +#define SSH2_MSG_KEXGSS_INIT 30 /* 0x1e */ + 'SSH2_MSG_KEXGSS_INIT' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXGSS_CONTINUE 31 /* 0x1f */ + 'SSH2_MSG_KEXGSS_CONTINUE' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXGSS_COMPLETE 32 /* 0x20 */ + 'SSH2_MSG_KEXGSS_COMPLETE' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXGSS_HOSTKEY 33 /* 0x21 */ + 'SSH2_MSG_KEXGSS_HOSTKEY' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXGSS_ERROR 34 /* 0x22 */ + 'SSH2_MSG_KEXGSS_ERROR' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXGSS_GROUPREQ 40 /* 0x28 */ + 'SSH2_MSG_KEXGSS_GROUPREQ' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, +#define SSH2_MSG_KEXGSS_GROUP 41 /* 0x29 */ + 'SSH2_MSG_KEXGSS_GROUP' => sub { + my ($direction, $seq, $data) = @_; + print "\n"; + }, #define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ 'SSH2_MSG_KEXRSA_PUBKEY' => sub { my ($direction, $seq, $data) = @_; @@ -533,6 +580,221 @@ }, ); +our %disc_reasons = ( + 1 => "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT", + 2 => "SSH_DISCONNECT_PROTOCOL_ERROR", + 3 => "SSH_DISCONNECT_KEY_EXCHANGE_FAILED", + 4 => "SSH_DISCONNECT_RESERVED", + 5 => "SSH_DISCONNECT_MAC_ERROR", + 6 => "SSH_DISCONNECT_COMPRESSION_ERROR", + 7 => "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE", + 8 => "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED", + 9 => "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE", + 10 => "SSH_DISCONNECT_CONNECTION_LOST", + 11 => "SSH_DISCONNECT_BY_APPLICATION", + 12 => "SSH_DISCONNECT_TOO_MANY_CONNECTIONS", + 13 => "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER", + 14 => "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE", + 15 => "SSH_DISCONNECT_ILLEGAL_USER_NAME", +); + +my %verbose_packet_dump_functions = ( + 'SSH2_MSG_KEXINIT' => sub { + my ($data) = @_; + my ($cookie0, $cookie1, $cookie2, $cookie3, + $kex, $hostkey, $cscipher, $sccipher, $csmac, $scmac, + $cscompress, $sccompress, $cslang, $sclang, $guess, $reserved) = + &parse("uuuussssssssssbu", $data); + printf(" cookie: %08x%08x%08x%08x\n", + $cookie0, $cookie1, $cookie2, $cookie3); + my $print_namelist = sub { + my @names = split /,/, $_[1]; + printf " %s: name-list with %d items%s\n", $_[0], (scalar @names), + join "", map { "\n $_" } @names; + }; + $print_namelist->("kex", $kex); + $print_namelist->("host key", $hostkey); + $print_namelist->("client->server cipher", $cscipher); + $print_namelist->("server->client cipher", $sccipher); + $print_namelist->("client->server MAC", $csmac); + $print_namelist->("server->client MAC", $scmac); + $print_namelist->("client->server compression", $cscompress); + $print_namelist->("server->client compression", $sccompress); + $print_namelist->("client->server language", $cslang); + $print_namelist->("server->client language", $sclang); + printf " first kex packet follows: %s\n", $guess; + printf " reserved field: %#x\n", $reserved; + }, + 'SSH2_MSG_KEXDH_INIT' => sub { + my ($data) = @_; + my ($e) = &parse("m", $data); + printf " e: %s\n", $e; + }, + 'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub { + my ($data) = @_; + my ($min, $pref, $max) = &parse("uuu", $data); + printf " min bits: %d\n", $min; + printf " preferred bits: %d\n", $pref; + printf " max bits: %d\n", $max; + }, + 'SSH2_MSG_KEX_DH_GEX_GROUP' => sub { + my ($data) = @_; + my ($p, $g) = &parse("mm", $data); + printf " p: %s\n", $p; + printf " g: %s\n", $g; + }, + 'SSH2_MSG_KEX_DH_GEX_INIT' => sub { + my ($data) = @_; + my ($e) = &parse("m", $data); + printf " e: %s\n", $e; + }, + 'SSH2_MSG_KEX_ECDH_INIT' => sub { + my ($data) = @_; + my ($cpv) = &parse("s", $data); + # Public values in ECDH depend for their interpretation on the + # selected curve, and this script doesn't cross-analyse the + # two KEXINIT packets to independently figure out what that + # curve is. So the best we can do is just dump the raw data. + printf " client public value: %s\n", (unpack "H*", $cpv); + }, + 'SSH2_MSG_KEXDH_REPLY' => sub { + my ($data) = @_; + my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data); + my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); + printf " host key: %s\n", $hktype; + while (@hostkey) { + my ($key, $value) = splice @hostkey, 0, 2; + printf " $key: $value\n"; + } + printf " f: %s\n", $f; + printf " signature:\n"; + my @signature = &parse_signature($sigblob, $hktype); + while (@signature) { + my ($key, $value) = splice @signature, 0, 2; + printf " $key: $value\n"; + } + }, + 'SSH2_MSG_KEX_DH_GEX_REPLY' => sub { + my ($data) = @_; + my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data); + my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); + printf " host key: %s\n", $hktype; + while (@hostkey) { + my ($key, $value) = splice @hostkey, 0, 2; + printf " $key: $value\n"; + } + printf " f: %s\n", $f; + printf " signature:\n"; + my @signature = &parse_signature($sigblob, $hktype); + while (@signature) { + my ($key, $value) = splice @signature, 0, 2; + printf " $key: $value\n"; + } + }, + 'SSH2_MSG_KEX_ECDH_REPLY' => sub { + my ($data) = @_; + my ($hostkeyblob, $spv, $sigblob) = &parse("sss", $data); + my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); + printf " host key: %s\n", $hktype; + while (@hostkey) { + my ($key, $value) = splice @hostkey, 0, 2; + printf " $key: $value\n"; + } + printf " server public value: %s\n", (unpack "H*", $spv); + printf " signature:\n"; + my @signature = &parse_signature($sigblob, $hktype); + while (@signature) { + my ($key, $value) = splice @signature, 0, 2; + printf " $key: $value\n"; + } + }, + 'SSH2_MSG_NEWKEYS' => sub {}, + 'SSH2_MSG_SERVICE_REQUEST' => sub { + my ($data) = @_; + my ($servname) = &parse("s", $data); + printf " service name: %s\n", $servname; + }, + 'SSH2_MSG_SERVICE_ACCEPT' => sub { + my ($data) = @_; + my ($servname) = &parse("s", $data); + printf " service name: %s\n", $servname; + }, + 'SSH2_MSG_DISCONNECT' => sub { + my ($data) = @_; + my ($reason, $desc, $lang) = &parse("uss", $data); + printf(" reason code: %d%s\n", $reason, + defined $disc_reasons{$reason} ? + " ($disc_reasons{$reason})" : "" ); + printf " description: '%s'\n", $desc; + printf " language tag: '%s'\n", $lang; + }, + 'SSH2_MSG_DEBUG' => sub { + my ($data) = @_; + my ($display, $desc, $lang) = &parse("bss", $data); + printf " always display: %s\n", $display; + printf " description: '%s'\n", $desc; + printf " language tag: '%s'\n", $lang; + }, + 'SSH2_MSG_IGNORE' => sub { + my ($data) = @_; + my ($payload) = &parse("s", $data); + printf " data: %s\n", unpack "H*", $payload; + }, + 'SSH2_MSG_UNIMPLEMENTED' => sub { + my ($data) = @_; + my ($seq) = &parse("u", $data); + printf " sequence number: %d\n", $seq; + }, + 'SSH2_MSG_KEXGSS_INIT' => sub { + my ($data) = @_; + my ($token, $e) = &parse("sm", $data); + printf " output token: %s\n", unpack "H*", $token; + printf " e: %s\n", $e; + }, + 'SSH2_MSG_KEXGSS_CONTINUE' => sub { + my ($data) = @_; + my ($token) = &parse("s", $data); + printf " output token: %s\n", unpack "H*", $token; + }, + 'SSH2_MSG_KEXGSS_COMPLETE' => sub { + my ($data) = @_; + my ($f, $permsgtoken, $got_output) = &parse("msb", $data); + printf " f: %s\n", $f; + printf " per-message token: %s\n", unpack "H*", $permsgtoken; + printf " output token present: %s\n", $got_output; + if ($got_output eq "yes") { + my ($token) = &parse("s", $data); + printf " output token: %s\n", unpack "H*", $token; + } + }, + 'SSH2_MSG_KEXGSS_HOSTKEY' => sub { + my ($data) = @_; + my ($hostkey) = &parse("s", $data); + printf " host key: %s\n", unpack "H*", $hostkey; + }, + 'SSH2_MSG_KEXGSS_ERROR' => sub { + my ($data) = @_; + my ($maj, $min, $msg, $lang) = &parse("uuss", $data); + printf " major status: %d\n", $maj; + printf " minor status: %d\n", $min; + printf " message: '%s'\n", $msg; + printf " language tag: '%s'\n", $lang; + }, + 'SSH2_MSG_KEXGSS_GROUPREQ' => sub { + my ($data) = @_; + my ($min, $pref, $max) = &parse("uuu", $data); + printf " min bits: %d\n", $min; + printf " preferred bits: %d\n", $pref; + printf " max bits: %d\n", $max; + }, + 'SSH2_MSG_KEXGSS_GROUP' => sub { + my ($data) = @_; + my ($p, $g) = &parse("mm", $data); + printf " p: %s\n", $p; + printf " g: %s\n", $g; + }, +); + my %sftp_packets = ( #define SSH_FXP_INIT 1 /* 0x1 */ 0x1 => sub { @@ -760,6 +1022,12 @@ }, ); +for my $type (keys %verbose_packet) { + if (!defined $verbose_packet_dump_functions{$type}) { + die "no verbose dump available for packet type $type\n"; + } +} + my ($direction, $seq, $ourseq, $type, $data, $recording); my %ourseqs = ('i'=>0, 'o'=>0); @@ -772,11 +1040,26 @@ $recording = 0; my $fullseq = "$direction$ourseq"; print "$fullseq: $type "; + + my ($verbose_dump, $verbose_data) = undef; + if (defined $verbose_packet_dump_functions{$type} && + ($verbose_all || defined $verbose_packet{$type})) { + $verbose_dump = $verbose_packet_dump_functions{$type}; + $verbose_data = [ @$data ]; + } + if (defined $packets{$type}) { $packets{$type}->($direction, $fullseq, $data); } else { printf "raw %s\n", join "", map { sprintf "%02x", $_ } @$data; } + if (defined $verbose_dump) { + $verbose_dump->($verbose_data); + if (@$verbose_data) { + printf(" trailing bytes: %s\n", + unpack "H*", pack "C*", @$verbose_data); + } + } } } if (/^(Incoming|Outgoing) packet #0x([0-9a-fA-F]+), type \d+ \/ 0x[0-9a-fA-F]+ \((.*)\)/) { @@ -791,6 +1074,9 @@ $data = []; $recording = 1; } + if ($pass_through_events && m/^Event Log: ([^\n]*)$/) { + printf "event: $1\n"; + } } if ($dumpchannels) { @@ -809,6 +1095,13 @@ } } +sub format_unsigned_hex_integer { + my $abs = join "", map { sprintf "%02x", $_ } @_; + $abs =~ s!^0*!!g; + $abs = "0" if $abs eq ""; + return "0x" . $abs; +} + sub parseone { my ($type, $data) = @_; if ($type eq "u") { # uint32 @@ -834,7 +1127,7 @@ sub parseone { my $len = unpack "N", pack "C*", @bytes; @bytes = splice @$data, 0, $len; return "" if @bytes < $len or grep { $_<0 } @bytes; - if ($type eq "mpint") { + if ($type eq "m") { my $str = ""; if ($bytes[0] >= 128) { # Take two's complement. @@ -849,7 +1142,7 @@ sub parseone { } $str = "-"; } - $str .= "0x" . join "", map { sprintf "%02x", $_ } @bytes; + $str .= &format_unsigned_hex_integer(@bytes); return $str; } else { return pack "C*", @bytes; @@ -954,6 +1247,80 @@ sub sftp_parse_attrs { return $out; } +sub parse_public_key { + my ($blob) = @_; + my $data = [ unpack "C*", $blob ]; + my @toret; + my ($type) = &parse("s", $data); + push @toret, $type; + if ($type eq "ssh-rsa") { + my ($e, $n) = &parse("mm", $data); + push @toret, "e", $e, "n", $n; + } elsif ($type eq "ssh-dss") { + my ($p, $q, $g, $y) = &parse("mmmm", $data); + push @toret, "p", $p, "q", $q, "g", $g, "y", $y; + } elsif ($type eq "ssh-ed25519") { + my ($xyblob) = &parse("s", $data); + my @y = unpack "C*", $xyblob; + push @toret, "hibit(x)", $y[$#y] & 1; + $y[$#y] &= ~1; + push @toret, "y & ~1", &format_unsigned_hex_integer(@y); + } elsif ($type =~ m!^ecdsa-sha2-nistp(256|384|521)$!) { + my ($curvename, $blob) = &parse("ss", $data); + push @toret, "curve name", $curvename; + my @blobdata = unpack "C*", $blob; + my ($fmt) = &parse("B", \@blobdata); + push @toret, "format byte", $fmt; + if ($fmt == 4) { + push @toret, "x", &format_unsigned_hex_integer( + @blobdata[0..($#blobdata+1)/2-1]); + push @toret, "y", &format_unsigned_hex_integer( + @blobdata[($#blobdata+1)/2..$#blobdata]); + } + } else { + push @toret, "undecoded data", unpack "H*", pack "C*", @$data; + } + return @toret; +}; + +sub parse_signature { + my ($blob, $keytype) = @_; + my $data = [ unpack "C*", $blob ]; + my @toret; + if ($keytype eq "ssh-rsa") { + my ($type, $s) = &parse("ss", $data); + push @toret, "sig type", $type; + push @toret, "s", &format_unsigned_hex_integer(unpack "C*", $s); + } elsif ($keytype eq "ssh-dss") { + my ($type, $subblob) = &parse("ss", $data); + push @toret, "sig type", $type; + push @toret, "r", &format_unsigned_hex_integer( + unpack "C*", substr($subblob, 0, 20)); + push @toret, "s", &format_unsigned_hex_integer( + unpack "C*", substr($subblob, 20, 40)); + } elsif ($keytype eq "ssh-ed25519") { + my ($type, $rsblob) = &parse("ss", $data); + push @toret, "sig type", $type; + my @ry = unpack "C*", $rsblob; + my @sy = splice @ry, 32, 32; + push @toret, "hibit(r.x)", $ry[$#ry] & 1; + $ry[$#ry] &= ~1; + push @toret, "r.y & ~1", &format_unsigned_hex_integer(@ry); + push @toret, "hibit(s.x)", $sy[$#sy] & 1; + $sy[$#sy] &= ~1; + push @toret, "s.y & ~1", &format_unsigned_hex_integer(@sy); + } elsif ($keytype =~ m!^ecdsa-sha2-nistp(256|384|521)$!) { + my ($sigtype, $subblob) = &parse("ss", $data); + push @toret, "sig type", $sigtype; + my @sbdata = unpack "C*", $subblob; + my ($r, $s) = &parse("mm", \@sbdata); + push @toret, "r", $r, "s", $s; + } else { + push @toret, "undecoded data", unpack "H*", pack "C*", @$data; + } + return @toret; +}; + sub stringescape { my ($str) = @_; $str =~ s!\\!\\\\!g; diff --git a/cproxy.c b/cproxy.c index 4f9a3ffa..0fd4a8ec 100644 --- a/cproxy.c +++ b/cproxy.c @@ -9,19 +9,19 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #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) { - void *hmacmd5_ctx; + struct hmacmd5_context *hmacmd5_ctx; int pwlen; - hmacmd5_ctx = hmacmd5_make_context(NULL); + hmacmd5_ctx = hmacmd5_make_context(); pwlen = strlen(passwd); if (pwlen>64) { @@ -36,13 +36,12 @@ static void hmacmd5_chap(const unsigned char *challenge, int challen, hmacmd5_free_context(hmacmd5_ctx); } -void proxy_socks5_offerencryptedauth(char *command, int *len) +void proxy_socks5_offerencryptedauth(BinarySink *bs) { - command[*len] = 0x03; /* CHAP */ - (*len)++; + put_byte(bs, 0x03); /* CHAP */ } -int proxy_socks5_handlechap (Proxy_Socket p) +int proxy_socks5_handlechap (ProxySocket *p) { /* CHAP authentication reply format: @@ -132,7 +131,7 @@ int proxy_socks5_handlechap (Proxy_Socket p) hmacmd5_chap(data, p->chap_current_datalen, conf_get_str(p->conf, CONF_proxy_password), &outbuf[4]); - sk_write(p->sub_socket, (char *)outbuf, 20); + sk_write(p->sub_socket, outbuf, 20); break; case 0x11: /* Chose a protocol */ @@ -158,7 +157,7 @@ int proxy_socks5_handlechap (Proxy_Socket p) return 0; } -int proxy_socks5_selectchap(Proxy_Socket p) +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); diff --git a/defs.h b/defs.h new file mode 100644 index 00000000..6a789389 --- /dev/null +++ b/defs.h @@ -0,0 +1,115 @@ +/* + * defs.h: initial definitions for PuTTY. + * + * The rule about this header file is that it can't depend on any + * other header file in this code base. This is where we define + * things, as much as we can, that other headers will want to refer + * to, such as opaque structure types and their associated typedefs, + * or macros that are used by other headers. + */ + +#ifndef PUTTY_DEFS_H +#define PUTTY_DEFS_H + +#include +#include +#include + +#if defined _MSC_VER && _MSC_VER < 1800 +/* Work around lack of inttypes.h in older MSVC */ +#define PRIu64 "I64u" +#define SCNu64 "I64u" +#else +#include +#endif + +typedef struct conf_tag Conf; +typedef struct terminal_tag Terminal; + +typedef struct Filename Filename; +typedef struct FontSpec FontSpec; + +typedef struct bufchain_tag bufchain; + +typedef struct strbuf strbuf; + +struct RSAKey; + +typedef struct BinarySink BinarySink; +typedef struct BinarySource BinarySource; + +typedef struct IdempotentCallback IdempotentCallback; + +typedef struct SockAddr SockAddr; + +typedef struct Socket Socket; +typedef struct Plug Plug; +typedef struct SocketPeerInfo SocketPeerInfo; + +typedef struct Backend Backend; +typedef struct BackendVtable BackendVtable; + +typedef struct Ldisc_tag Ldisc; +typedef struct LogContext LogContext; +typedef struct LogPolicy LogPolicy; +typedef struct LogPolicyVtable LogPolicyVtable; + +typedef struct Seat Seat; +typedef struct SeatVtable SeatVtable; + +typedef struct TermWin TermWin; +typedef struct TermWinVtable TermWinVtable; + +typedef struct Ssh Ssh; + +typedef struct SftpServer SftpServer; +typedef struct SftpServerVtable SftpServerVtable; + +typedef struct Channel Channel; +typedef struct SshChannel SshChannel; +typedef struct mainchan mainchan; + +typedef struct ssh_sharing_state ssh_sharing_state; +typedef struct ssh_sharing_connstate ssh_sharing_connstate; +typedef struct share_channel share_channel; + +typedef struct PortFwdManager PortFwdManager; +typedef struct PortFwdRecord PortFwdRecord; +typedef struct ConnectionLayer ConnectionLayer; + +typedef struct dlgparam dlgparam; + +typedef struct settings_w settings_w; +typedef struct settings_r settings_r; +typedef struct settings_e settings_e; + +typedef struct SessionSpecial SessionSpecial; + +/* + * A small structure wrapping up a (pointer, length) pair so that it + * can be conveniently passed to or from a function. + */ +typedef struct ptrlen { + const void *ptr; + size_t len; +} ptrlen; + +typedef struct logblank_t logblank_t; + +typedef struct BinaryPacketProtocol BinaryPacketProtocol; +typedef struct PacketProtocolLayer PacketProtocolLayer; + +/* 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 + * *evaluates* exactly one copy of it, so it's side-effect safe. */ +#define TYPECHECK(to_check, to_return) \ + (sizeof(to_check) ? (to_return) : (to_return)) + +/* Return a pointer to the object of structure type 'type' whose field + * with name 'field' is pointed at by 'object'. */ +#define container_of(object, type, field) \ + TYPECHECK(object == &((type *)0)->field, \ + ((type *)(((char *)(object)) - offsetof(type, field)))) + +#endif /* PUTTY_DEFS_H */ diff --git a/dialog.c b/dialog.c index 31a9627b..d00e2529 100644 --- a/dialog.c +++ b/dialog.c @@ -86,7 +86,7 @@ void ctrl_free_set(struct controlset *s) * path. If that path doesn't exist, return the index where it * should be inserted. */ -static int ctrl_find_set(struct controlbox *b, const char *path, int start) +static int ctrl_find_set(struct controlbox *b, const char *path, bool start) { int i, last, thisone; @@ -115,7 +115,7 @@ static int ctrl_find_set(struct controlbox *b, const char *path, int start) int ctrl_find_path(struct controlbox *b, const char *path, int index) { if (index < 0) - index = ctrl_find_set(b, path, 1); + index = ctrl_find_set(b, path, true); else index++; @@ -131,7 +131,7 @@ struct controlset *ctrl_settitle(struct controlbox *b, { struct controlset *s = snew(struct controlset); - int index = ctrl_find_set(b, path, 1); + int index = ctrl_find_set(b, path, true); s->pathname = dupstr(path); s->boxname = NULL; s->boxtitle = dupstr(title); @@ -155,7 +155,7 @@ struct controlset *ctrl_getset(struct controlbox *b, const char *path, const char *name, const char *boxtitle) { struct controlset *s; - int index = ctrl_find_set(b, path, 1); + int index = ctrl_find_set(b, path, true); while (index < b->nctrlsets && !strcmp(b->ctrlsets[index]->pathname, path)) { if (b->ctrlsets[index]->boxname && @@ -227,7 +227,7 @@ static union control *ctrl_new(struct controlset *s, int type, * Fill in the standard fields. */ c->generic.type = type; - c->generic.tabdelay = 0; + c->generic.tabdelay = false; c->generic.column = COLUMN_FIELD(0, s->ncolumns); c->generic.helpctx = helpctx; c->generic.handler = handler; @@ -266,8 +266,8 @@ union control *ctrl_editbox(struct controlset *s, const char *label, c->editbox.label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; - c->editbox.password = 0; - c->editbox.has_list = 0; + c->editbox.password = false; + c->editbox.has_list = false; c->editbox.context2 = context2; return c; } @@ -281,8 +281,8 @@ union control *ctrl_combobox(struct controlset *s, const char *label, c->editbox.label = label ? dupstr(label) : NULL; c->editbox.shortcut = shortcut; c->editbox.percentwidth = percentage; - c->editbox.password = 0; - c->editbox.has_list = 1; + c->editbox.password = false; + c->editbox.has_list = true; c->editbox.context2 = context2; return c; } @@ -346,8 +346,8 @@ union control *ctrl_pushbutton(struct controlset *s, const char *label, union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); c->button.label = label ? dupstr(label) : NULL; c->button.shortcut = shortcut; - c->button.isdefault = 0; - c->button.iscancel = 0; + c->button.isdefault = false; + c->button.iscancel = false; return c; } @@ -359,12 +359,12 @@ union control *ctrl_listbox(struct controlset *s, const char *label, c->listbox.label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 5; /* *shrug* a plausible default */ - c->listbox.draglist = 0; + c->listbox.draglist = false; c->listbox.multisel = 0; c->listbox.percentwidth = 100; c->listbox.ncols = 0; c->listbox.percentages = NULL; - c->listbox.hscroll = TRUE; + c->listbox.hscroll = true; return c; } @@ -376,12 +376,12 @@ union control *ctrl_droplist(struct controlset *s, const char *label, c->listbox.label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 0; /* means it's a drop-down list */ - c->listbox.draglist = 0; + c->listbox.draglist = false; c->listbox.multisel = 0; c->listbox.percentwidth = percentage; c->listbox.ncols = 0; c->listbox.percentages = NULL; - c->listbox.hscroll = FALSE; + c->listbox.hscroll = false; return c; } @@ -393,17 +393,17 @@ union control *ctrl_draglist(struct controlset *s, const char *label, c->listbox.label = label ? dupstr(label) : NULL; c->listbox.shortcut = shortcut; c->listbox.height = 5; /* *shrug* a plausible default */ - c->listbox.draglist = 1; + c->listbox.draglist = true; c->listbox.multisel = 0; c->listbox.percentwidth = 100; c->listbox.ncols = 0; c->listbox.percentages = NULL; - c->listbox.hscroll = FALSE; + c->listbox.hscroll = false; return c; } union control *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, const char *filter, int write, + char shortcut, const char *filter, bool write, const char *title, intorptr helpctx, handler_fn handler, intorptr context) { diff --git a/dialog.h b/dialog.h index 9ac355c6..864471bb 100644 --- a/dialog.h +++ b/dialog.h @@ -2,12 +2,6 @@ * Exports and types from dialog.c. */ -/* - * This will come in handy for generic control handlers. Anyone - * knows how to make this more portable, let me know :-) - */ -#define ATOFFSET(data, offset) ( (void *) ( (char *)(data) + (offset) ) ) - /* * This is the big union which defines a single control, of any * type. @@ -109,13 +103,13 @@ enum { EVENT_SELCHANGE, EVENT_CALLBACK }; -typedef void (*handler_fn)(union control *ctrl, void *dlg, +typedef void (*handler_fn)(union control *ctrl, dlgparam *dp, void *data, int event); #define STANDARD_PREFIX \ int type; \ char *label; \ - int tabdelay; \ + bool tabdelay; \ int column; \ handler_fn handler; \ intorptr context; \ @@ -148,7 +142,7 @@ union control { * particular control should not yet appear in the tab * order. A subsequent CTRL_TABDELAY entry will place it. */ - int tabdelay; + bool tabdelay; /* * Indicate which column(s) this control occupies. This can * be unpacked into starting column and column span by the @@ -203,7 +197,7 @@ union control { * itself. */ int percentwidth; - int password; /* details of input are hidden */ + 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_- @@ -214,7 +208,7 @@ union control { * control; front ends are not required to support that * combination. */ - int has_list; + bool has_list; /* * Edit boxes tend to need two items of context, so here's * a spare. @@ -285,12 +279,12 @@ union control { * button', which gets implicitly pressed when you hit * Return even if it doesn't have the input focus. */ - int isdefault; + bool isdefault; /* * Also, the reverse of this: a default cancel-type button, * which is implicitly pressed when you hit Escape. */ - int iscancel; + bool iscancel; } button; struct { STANDARD_PREFIX; @@ -307,7 +301,7 @@ union control { * comfortable with). This is not guaranteed to work on a * drop-down list, so don't try it! */ - int draglist; + bool draglist; /* * If this is non-zero, the list can have more than one * element selected at a time. This is not guaranteed to @@ -346,11 +340,11 @@ union control { int ncols; /* number of columns */ int *percentages; /* % width of each column */ /* - * Flag which can be set to FALSE to suppress the horizontal + * Flag which can be set to false to suppress the horizontal * scroll bar if a list box entry goes off the right-hand * side. */ - int hscroll; + bool hscroll; } listbox; struct { STANDARD_PREFIX; @@ -380,7 +374,7 @@ union control { * choosing a file to read or one to write (and possibly * create). */ - int for_writing; + bool for_writing; /* * On at least some platforms, the file selector is a * separate dialog box, and contains a user-settable title. @@ -523,7 +517,7 @@ 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, int write, + 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, @@ -540,16 +534,16 @@ union control *ctrl_tabdelay(struct controlset *, union control *); * Routines the platform-independent dialog code can call to read * and write the values of controls. */ -void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton); -int dlg_radiobutton_get(union control *ctrl, void *dlg); -void dlg_checkbox_set(union control *ctrl, void *dlg, int checked); -int dlg_checkbox_get(union control *ctrl, void *dlg); -void dlg_editbox_set(union control *ctrl, void *dlg, char const *text); -char *dlg_editbox_get(union control *ctrl, void *dlg); /* result must be freed by caller */ +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 */ /* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(union control *ctrl, void *dlg); -void dlg_listbox_del(union control *ctrl, void *dlg, int index); -void dlg_listbox_add(union control *ctrl, void *dlg, char const *text); +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); /* * Each listbox entry may have a numeric id associated with it. * Note that some front ends only permit a string to be stored at @@ -557,53 +551,53 @@ void dlg_listbox_add(union control *ctrl, void *dlg, 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, void *dlg, +void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, char const *text, int id); -int dlg_listbox_getid(union control *ctrl, void *dlg, int index); +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, void *dlg); -int dlg_listbox_issel(union control *ctrl, void *dlg, int index); -void dlg_listbox_select(union control *ctrl, void *dlg, int index); -void dlg_text_set(union control *ctrl, void *dlg, char const *text); -void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn); -Filename *dlg_filesel_get(union control *ctrl, void *dlg); -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fn); -FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg); +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); /* * Bracketing a large set of updates in these two functions will * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, void *dlg); -void dlg_update_done(union control *ctrl, void *dlg); +void dlg_update_start(union control *ctrl, dlgparam *dp); +void dlg_update_done(union control *ctrl, dlgparam *dp); /* * Set input focus into a particular control. */ -void dlg_set_focus(union control *ctrl, void *dlg); +void dlg_set_focus(union control *ctrl, dlgparam *dp); /* * Change the label text on a control. */ -void dlg_label_change(union control *ctrl, void *dlg, char const *text); +void dlg_label_change(union control *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, void *dlg); +union control *dlg_last_focused(union control *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 * error; dlg_error() puts up a message-box or equivalent. */ -void dlg_beep(void *dlg); -void dlg_error_msg(void *dlg, const char *msg); +void dlg_beep(dlgparam *dp); +void dlg_error_msg(dlgparam *dp, const char *msg); /* * This function signals to the front end that the dialog's * processing is completed, and passes an integer value (typically * a success status). */ -void dlg_end(void *dlg, int value); +void dlg_end(dlgparam *dp, int value); /* * Routines to manage a (per-platform) colour selector. @@ -618,10 +612,10 @@ void dlg_end(void *dlg, 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, void *dlg, +void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b); -int dlg_coloursel_results(union control *ctrl, void *dlg, - int *r, int *g, int *b); +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b); /* * This routine is used by the platform-independent code to @@ -632,7 +626,7 @@ int dlg_coloursel_results(union control *ctrl, void *dlg, * If `ctrl' is NULL, _all_ controls in the dialog get refreshed * (for loading or saving entire sets of settings). */ -void dlg_refresh(union control *ctrl, void *dlg); +void dlg_refresh(union control *ctrl, dlgparam *dp); /* * Standard helper functions for reading a controlbox structure. diff --git a/doc/Makefile b/doc/Makefile index e7bf287e..cb079fb5 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -44,19 +44,17 @@ INPUTS = $(patsubst %,%.but,$(CHAPTERS)) HALIBUT = halibut index.html: $(INPUTS) - $(HALIBUT) --text --html --winhelp $(INPUTS) + $(HALIBUT) --text --html --winhelp --chm $(INPUTS) -# During formal builds it's useful to be able to build this one alone. +# During formal builds it's useful to be able to build these ones alone. putty.hlp: $(INPUTS) $(HALIBUT) --winhelp $(INPUTS) +putty.chm: $(INPUTS) + $(HALIBUT) --chm $(INPUTS) putty.info: $(INPUTS) $(HALIBUT) --info $(INPUTS) -chm: putty.hhp -putty.hhp: $(INPUTS) chm.but - $(HALIBUT) --html $(INPUTS) chm.but - MKMAN = $(HALIBUT) --man=$@ mancfg.but $< MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \ pageant.1 diff --git a/doc/blurb.but b/doc/blurb.but index 227300f4..e5e03a60 100644 --- a/doc/blurb.but +++ b/doc/blurb.but @@ -7,17 +7,23 @@ \cfg{xhtml-leaf-contains-contents}{true} \cfg{xhtml-body-end}{

If you want to provide feedback on this manual or on the PuTTY tools themselves, see the -Feedback +Feedback page.

} \cfg{html-template-fragment}{%k}{%b} \cfg{info-max-file-size}{0} +\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} \cfg{winhelp-filename}{putty.hlp} \cfg{info-filename}{putty.info} +\cfg{chm-filename}{putty.chm} PuTTY is a free (MIT-licensed) Windows Telnet and SSH client. This manual documents PuTTY, and its companion utilities PSCP, PSFTP, diff --git a/doc/chm.but b/doc/chm.but deleted file mode 100644 index 44d1dca3..00000000 --- a/doc/chm.but +++ /dev/null @@ -1,22 +0,0 @@ -\# File containing the magic HTML configuration directives to create -\# an MS HTML Help project. We put this on the end of the PuTTY -\# docs build command line to build the HHP and friends. - -\cfg{html-leaf-level}{infinite} -\cfg{html-leaf-contains-contents}{false} -\cfg{html-suppress-navlinks}{true} -\cfg{html-suppress-address}{true} - -\cfg{html-contents-filename}{index.html} -\cfg{html-template-filename}{%k.html} -\cfg{html-template-fragment}{%k} - -\cfg{html-mshtmlhelp-chm}{putty.chm} -\cfg{html-mshtmlhelp-project}{putty.hhp} -\cfg{html-mshtmlhelp-contents}{putty.hhc} -\cfg{html-mshtmlhelp-index}{putty.hhk} - -\cfg{html-body-end}{} - -\cfg{html-head-end}{} - diff --git a/doc/config.but b/doc/config.but index eb96fc17..96323ce6 100644 --- a/doc/config.but +++ b/doc/config.but @@ -246,6 +246,15 @@ warned that the log file may not always be up to date as a result (although it will of course be flushed when it is closed, for instance at the end of a session). +\S{config-logheader} \I{log file, header}\q{Include header} + +\cfg{winhelp-topic}{logging.header} + +This option allows you to choose whether to include a header line +with the date and time when the log file is opened. It may be useful to +disable this if the log file is being used as realtime input to other +programs that don't expect the header line. + \S{config-logssh} Options specific to \i{SSH packet log}ging These options only apply if SSH packet data is being logged. @@ -499,8 +508,8 @@ instead of relying on the automatic detection. \cfg{winhelp-topic}{terminal.printing} A lot of VT100-compatible terminals support printing under control -of the remote server. PuTTY supports this feature as well, but it is -turned off by default. +of the remote server (sometimes called \q{passthrough printing}). +PuTTY supports this feature as well, but it is turned off by default. To enable remote-controlled printing, choose a printer from the \q{Printer to send ANSI printer output to} drop-down list box. This @@ -1381,31 +1390,47 @@ Note that this option only applies to line-drawing characters which characters that were received as Unicode code points will paste as Unicode always. -\H{config-selection} The Selection panel +\S{config-utf8linedraw} Combining VT100 line-drawing with UTF-8 -The Selection panel allows you to control the way \i{copy and paste} -work in the PuTTY window. +\cfg{winhelp-topic}{translation.utf8linedraw} -\S{config-rtfpaste} Pasting in \i{Rich Text Format} +If PuTTY is configured to treat data from the server as encoded in +UTF-8, then by default it disables the older VT100-style system of +control sequences that cause the lower-case letters to be temporarily +replaced by line drawing characters. -\cfg{winhelp-topic}{selection.rtf} +The rationale is that in UTF-8 mode you don't need those control +sequences anyway, because all the line-drawing characters they access +are available as Unicode characters already, so there's no need for +applications to put the terminal into a special state to get at them. -If you enable \q{Paste to clipboard in RTF as well as plain text}, -PuTTY will write formatting information to the clipboard as well as -the actual text you copy. The effect of this is -that if you paste into (say) a word processor, the text will appear -in the word processor in the same \i{font}, \i{colour}, and style -(e.g. bold, underline) PuTTY was using to display it. +Also, it removes a risk of the terminal \e{accidentally} getting into +that state: if you accidentally write uncontrolled binary data to a +non-UTF-8 terminal, it can be surprisingly common to find that your +next shell prompt appears as a sequence of line-drawing characters and +then you have to remember or look up how to get out of that mode. So +by default, UTF-8 mode simply doesn't \e{have} a confusing mode like +that to get into, accidentally or on purpose. -This option can easily be inconvenient, so by default it is -disabled. +However, not all applications will see it that way. Even UTF-8 +terminal users will still sometimes have to run software that tries to +print line-drawing characters in the old-fashioned way. So the +configuration option \q{Enable VT100 line drawing even in UTF-8 mode} +puts PuTTY into a hybrid mode in which it understands the VT100-style +control sequences that change the meaning of the ASCII lower case +letters, \e{and} understands UTF-8. + +\H{config-selection} The Selection panel + +The Selection panel allows you to control the way \i{copy and paste} +work in the PuTTY window. \S{config-mouse} Changing the actions of the mouse buttons \cfg{winhelp-topic}{selection.buttons} PuTTY's copy and paste mechanism is by default modelled on the Unix -\c{xterm} application. The X Window System uses a three-button mouse, +\i\c{xterm} application. The X Window System uses a three-button mouse, and the convention is that the \i{left button} \I{selecting text}selects, the \i{right button} extends an existing selection, and the \i{middle button} pastes. @@ -1469,13 +1494,112 @@ select a rectangular block. Using the \q{Default selection mode} control, you can set \i{rectangular selection} as the default, and then you have to hold down Alt to get the \e{normal} behaviour. -\S{config-charclasses} Configuring \i{word-by-word selection} +\S{config-clipboards} Assigning copy and paste actions to clipboards + +Here you can configure which clipboard(s) are written or read by +PuTTY's various copy and paste actions. + +The X Window System (which underlies most Unix graphical interfaces) +provides multiple clipboards (or \q{\i{selections}}), and many +applications support more than one of them by a different user +interface mechanism. + +The two most commonly used selections are called \cq{\i{PRIMARY}} and +\cq{\I{CLIPBOARD selection}CLIPBOARD}; in applications supporting both, +the usual behaviour is that \cw{PRIMARY} is used by mouse-only actions +(selecting text automatically copies it to \cw{PRIMARY}, and +\i{middle-clicking} pastes from \cw{PRIMARY}), whereas \cw{CLIPBOARD} +is used by explicit Copy and Paste menu items or keypresses such as +\i{Ctrl-C} and \i{Ctrl-V}. + +On other platforms such as Windows, where there is a single system +clipboard, PuTTY provides a second clipboard-like facility by permitting +you to paste the text you last selected in \e{this window}, whether or +not it is currently also in the system clipboard. This is not enabled +by default. + +\S2{config-selection-autocopy} \q{Auto-copy selected text} + +\cfg{winhelp-topic}{selection.autocopy} + +The checkbox \q{Auto-copy selected text to system clipboard} controls +whether or not selecting text in the PuTTY terminal window +automatically has the side effect of copying it to the system +clipboard, without requiring a separate user interface action. + +On X, the wording of this option is changed slightly so that +\cq{CLIPBOARD} is mentioned in place of the \q{system clipboard}. Text +selected in the terminal window will \e{always} be automatically +placed in the \cw{PRIMARY} selection, but if you tick this box, it +will \e{also} be placed in \cq{CLIPBOARD} at the same time. + +\S2{config-selection-clipactions} Choosing a clipboard for UI actions + +\cfg{winhelp-topic}{selection.clipactions} + +PuTTY has three user-interface actions which can be configured to +paste into the terminal (not counting menu items). You can click +whichever mouse button (if any) is configured to paste (see +\k{config-mouse}); you can press \i{Shift-Ins}; or you can press +\i{Ctrl-Shift-V}, although that action is not enabled by default. + +You can configure which of the available clipboards each of these +actions pastes from (including turning the paste action off +completely). On platforms with a single system clipboard, the +available options are to paste from that clipboard or to paste from +PuTTY's internal memory of the \i{last selected text} within that +window. On X, the standard options are \cw{CLIPBOARD} or \cw{PRIMARY}. + +(\cw{PRIMARY} is conceptually similar in that it \e{also} refers to +the last selected text \dash just across all applications instead of +just this window.) -\cfg{winhelp-topic}{selection.charclasses} +The two keyboard options each come with a corresponding key to copy +\e{to} the same clipboard. Whatever you configure Shift-Ins to paste +from, \i{Ctrl-Ins} will copy to the same location; similarly, +\i{Ctrl-Shift-C} will copy to whatever Ctrl-Shift-V pastes from. -PuTTY will select a word at a time in the terminal window if you -\i{double-click} to begin the drag. This panel allows you to control -precisely what is considered to be a word. +On X, you can also enter a selection name of your choice. For example, +there is a rarely-used standard selection called \cq{\i{SECONDARY}}, which +Emacs (for example) can work with if you hold down the Meta key while +dragging to select or clicking to paste; if you configure a PuTTY +keyboard action to access this clipboard, then you can interoperate +with other applications' use of it. Another thing you could do would +be to invent a clipboard name yourself, to create a special clipboard +shared \e{only} between instances of PuTTY, or between just instances +configured in that particular way. + +\S{config-paste-ctrl-char} \q{Permit control characters in pasted text} + +\cfg{winhelp-topic}{selection.pastectrl} + +It is possible for the clipboard to contain not just text (with +newlines and tabs) but also control characters such as ESC which could +have surprising effects if pasted into a terminal session, depending +on what program is running on the server side. Copying text from a +mischievous web page could put such characters onto the clipboard. + +By default, PuTTY filters out the more unusual control characters, +only letting through the more obvious text-formatting characters +(newlines, tab, backspace, and DEL). + +Setting this option stops this filtering; on paste, any character on +the clipboard is sent to the session uncensored. This might be useful +if you are deliberately using control character pasting as a simple +form of scripting, for instance. + +\H{config-selection-copy} The Copy panel + +The Copy configuration panel controls behaviour specifically related to +copying from the terminal window to the clipboard. + +\S{config-charclasses} Character classes + +\cfg{winhelp-topic}{copy.charclasses} + +PuTTY will \I{word-by-word selection}select a word at a time in the +terminal window if you \i{double-click} to begin the drag. This section +allows you to control precisely what is considered to be a word. Each character is given a \e{class}, which is a small number (typically 0, 1 or 2). PuTTY considers a single word to be any @@ -1511,6 +1635,20 @@ terminal (see \k{reset-terminal}). However, if you modify this option in mid-session using \q{Change Settings}, it will take effect immediately. +\S{config-rtfcopy} Copying in \i{Rich Text Format} + +\cfg{winhelp-topic}{copy.rtf} + +If you enable \q{Copy to clipboard in RTF as well as plain text}, +PuTTY will write formatting information to the clipboard as well as +the actual text you copy. The effect of this is +that if you paste into (say) a word processor, the text will appear +in the word processor in the same \i{font}, \i{colour}, and style +(e.g. bold, underline) PuTTY was using to display it. + +This option can easily be inconvenient, so by default it is +disabled. + \H{config-colours} The Colours panel The Colours panel allows you to control PuTTY's use of \i{colour}. @@ -1549,6 +1687,15 @@ If you do not see \cq{colors#256} in the output, you may need to change your terminal setting. On modern Linux machines, you could try \cq{xterm-256color}. +\S{config-truecolour} \q{Allow terminal to use 24-bit colour} + +\cfg{winhelp-topic}{colours.truecolour} + +This option is enabled by default. If it is disabled, PuTTY will +ignore any control sequences sent by the server which use the control +sequences supported by modern terminals to specify arbitrary 24-bit +RGB colour value. + \S{config-boldcolour} \q{Indicate bolded text by changing...} \cfg{winhelp-topic}{colours.bold} @@ -1821,9 +1968,10 @@ PuTTY will prompt for a username at the time you make a connection. In some environments, such as the networks of large organisations implementing \i{single sign-on}, a more sensible default may be to use the name of the user logged in to the local operating system (if any); -this is particularly likely to be useful with \i{GSSAPI} authentication -(see \k{config-ssh-auth-gssapi}). This control allows you to change -the default behaviour. +this is particularly likely to be useful with \i{GSSAPI} key exchange +and user authentication (see \k{config-ssh-auth-gssapi} and +\k{config-ssh-gssapi-kex}). This control allows you to change the default +behaviour. The current system username is displayed in the dialog as a convenience. It is not saved in the configuration; if a saved session @@ -2440,10 +2588,47 @@ well-known groups, if possible. effort on the part of the client, and somewhat less on the part of the server, than Diffie-Hellman key exchange. +\b \q{GSSAPI key exchange}: see \k{config-ssh-gssapi-kex}. + If the first algorithm PuTTY finds is below the \q{warn below here} line, you will see a warning box when you make the connection, similar to that for cipher selection (see \k{config-ssh-encryption}). +\S2{config-ssh-gssapi-kex} GSSAPI-based key exchange + +PuTTY supports a set of key exchange methods that also incorporates +GSSAPI-based authentication. They are enabled with the +\q{Attempt GSSAPI key exchange} checkbox (which also appears on the +\q{GSSAPI} panel). + +PuTTY can only perform the GSSAPI-authenticated key exchange methods +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. + +The advantage of doing GSSAPI authentication as part of the SSH key +exchange is apparent when you are using credential delegation (see +\k{config-ssh-auth-gssapi-delegation}). The SSH key exchange can be +repeated later in the session, and this allows your Kerberos V5 +credentials (which are typically short-lived) to be automatically +re-delegated to the server when they are refreshed on the client. +(This feature is commonly referred to as \q{\i{cascading credentials}}.) + +If your server doesn't support GSSAPI key exchange, it may still +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. + +Another effect of GSSAPI key exchange is that it replaces the usual +SSH mechanism of permanent host keys described in \k{gs-hostkey}. +So if you use this method, then you won't be asked any interactive +questions about whether to accept the server's host key. Instead, the +Kerberos exchange will verify the identity of the host you connect to, +at the same time as verifying your identity to it. + \S{config-ssh-kex-rekey} \ii{Repeat key exchange} \cfg{winhelp-topic}{ssh.kex.repeat} @@ -2486,6 +2671,14 @@ purposes, rekeys have much the same properties as keepalives. should bear that in mind when deciding whether to turn them off.) Note, however, the the SSH \e{server} can still initiate rekeys. +\b \q{Minutes between GSSAPI checks}, if you're using GSSAPI key +exchange, specifies how often the GSSAPI credential cache is checked +to see whether new tickets are available for delegation, or current +ones are near expiration. If forwarding of GSSAPI credentials is +enabled, PuTTY will try to rekey as necessary to keep the delegated +credentials from expiring. Frequent checks are recommended; rekeying +only happens when needed. + \b \q{Max data before rekey} specifies the amount of data (in bytes) that is permitted to flow in either direction before a rekey is initiated. If this is set to zero, PuTTY will not rekey due to @@ -2507,7 +2700,7 @@ used: Disabling data-based rekeys entirely is a bad idea. The \i{integrity}, and to a lesser extent, \i{confidentiality} of the SSH-2 protocol depend -in part on rekeys occuring before a 32-bit packet sequence number +in part on rekeys occurring before a 32-bit packet sequence number wraps around. Unlike time-based rekeys, data-based rekeys won't occur when the SSH connection is idle, so they shouldn't cause the same problems. The SSH-1 protocol, incidentally, has even weaker integrity @@ -2839,15 +3032,28 @@ machine, which in principle can authenticate in many different ways but in practice is usually used with the \i{Kerberos} \i{single sign-on} protocol to implement \i{passwordless login}. -GSSAPI is only available in the SSH-2 protocol. +GSSAPI authentication is only available in the SSH-2 protocol. + +PuTTY supports two forms of GSSAPI-based authentication. In one of +them, the SSH key exchange happens in the normal way, and GSSAPI is +only involved in authenticating the user. The checkbox labelled +\q{Attempt GSSAPI authentication} controls this form. -The topmost control on the GSSAPI subpanel is the checkbox labelled -\q{Attempt GSSAPI authentication}. If this is disabled, GSSAPI will -not be attempted at all and the rest of this panel is unused. If it -is enabled, GSSAPI authentication will be attempted, and (typically) -if your client machine has valid Kerberos credentials loaded, then -PuTTY should be able to authenticate automatically to servers that -support Kerberos logins. +In the other method, GSSAPI-based authentication is combined with the +SSH key exchange phase. If this succeeds, then the SSH authentication +step has nothing left to do. See \k{config-ssh-gssapi-kex} for more +information about this method. The checkbox labelled \q{Attempt GSSAPI +key exchange} controls this form. (The same checkbox appears on the +\q{Kex} panel.) + +If one or both of these controls is enabled, then GSSAPI +authentication will be attempted in one form or the other, and +(typically) if your client machine has valid Kerberos credentials +loaded, then PuTTY should be able to authenticate automatically to +servers that support Kerberos logins. + +If both of those checkboxes are disabled, PuTTY will not try any form +of GSSAPI at all, and the rest of this panel will be unused. \S{config-ssh-auth-gssapi-delegation} \q{Allow GSSAPI credential delegation} @@ -2875,6 +3081,10 @@ administrator of one server is likely to already have access to the other services too; so this would typically be less of a risk than SSH agent forwarding. +If your connection is not using GSSAPI key exchange, it is possible +for the delegation to expire during your session. See +\k{config-ssh-gssapi-kex} for more information. + \S{config-ssh-auth-gssapi-libraries} Preference order for GSSAPI libraries @@ -2885,11 +3095,11 @@ method to be accessed through the same interface. Therefore, more than one authentication library may exist on your system which can be accessed using GSSAPI. -PuTTY contains native support for a few well-known such libraries, -and will look for all of them on your system and use whichever it -finds. If more than one exists on your system and you need to use a -specific one, you can adjust the order in which it will search using -this preference list control. +PuTTY contains native support for a few well-known such libraries +(including Windows' \i{SSPI}), and will look for all of them on your system +and use whichever it finds. If more than one exists on your system and +you need to use a specific one, you can adjust the order in which it +will search using this preference list control. One of the options in the preference list is to use a user-specified GSSAPI library. If the library you want to use is not mentioned by @@ -2959,7 +3169,7 @@ modes from the local terminal, if any. } -\b If \q{Nothing} is selected, no value for the mode will not be +\b If \q{Nothing} is selected, no value for the mode will be specified to the server under any circumstances. \b If a value is specified, it will be sent to the server under all @@ -3006,18 +3216,19 @@ PuTTY in a variety of ways, such as \cw{true}/\cw{false}, \cw{no} is different from not sending the mode at all.) \b The boolean mode \I{IUTF8 terminal mode}\cw{IUTF8} signals to the -server whether the terminal character set is \i{UTF-8} or not. -If this is set incorrectly, keys like backspace may do the wrong thing -in some circumstances. However, setting this is not usually -sufficient to cause servers to expect the terminal to be in UTF-8 mode; -POSIX servers will generally require the locale to be set (by some -server-dependent means), although many default to UTF-8. Also, -since this mode was added to the SSH protocol much later than the -others, \#{circa 2016} many servers (particularly older servers) do -not honour this mode sent over SSH; indeed, a few poorly-written -servers object to its mere presence, so you may find you need to set -it to not be sent at all. When set to \q{Auto}, this follows the local -configured character set (see \k{config-charset}). +server whether the terminal character set is \i{UTF-8} or not, for +purposes such as basic line editing; if this is set incorrectly, +the backspace key may erase the wrong amount of text, for instance. +However, simply setting this is not usually sufficient for the server +to use UTF-8; POSIX servers will generally also require the locale to +be set (by some server-dependent means), although many newer +installations default to UTF-8. Also, since this mode was added to the +SSH protocol much later than the others, \#{circa 2016} many servers +(particularly older servers) do not honour this mode sent over SSH; +indeed, a few poorly-written servers object to its mere presence, so +you may find you need to set it to not be sent at all. When set to +\q{Auto}, this follows the local configured character set (see +\k{config-charset}). \b Terminal speeds are configured elsewhere; see \k{config-termspeed}. diff --git a/doc/errors.but b/doc/errors.but index fdbdd861..8e353fb9 100644 --- a/doc/errors.but +++ b/doc/errors.but @@ -122,9 +122,7 @@ ridiculous amount of memory, and will terminate with an \q{Out of memory} error. This can happen in SSH-2, if PuTTY and the server have not enabled -encryption in the same way (see \k{faq-outofmem} in the FAQ). Some -versions of \i{OpenSSH} have a known problem with this: see -\k{faq-openssh-bad-openssl}. +encryption in the same way (see \k{faq-outofmem} in the FAQ). This can also happen in PSCP or PSFTP, if your \i{login scripts} on the server generate output: the client program will be expecting an SFTP @@ -233,8 +231,13 @@ protection (such as HTTP) will manifest in more subtle failures (such as misdisplayed text or images in a web browser) which may not be noticed. -A known server problem which can cause this error is described in -\k{faq-openssh-bad-openssl} in the FAQ. +Occasionally this has been caused by server bugs. An example is the +bug described at \k{config-ssh-bug-hmac2}, although you're very +unlikely to encounter that one these days. + +In this context MAC stands for \ii{Message Authentication Code}. It's a +cryptographic term, and it has nothing at all to do with Ethernet +MAC (Media Access Control) addresses, or with the Apple computer. \H{errors-garbled} \q{Incoming packet was garbled on decryption} @@ -247,10 +250,7 @@ in the server, or in between. If you get this error, one thing you could try would be to fiddle with the setting of \q{Miscomputes SSH-2 encryption keys} (see \k{config-ssh-bug-derivekey2}) or \q{Ignores SSH-2 maximum packet -size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel . - -Another known server problem which can cause this error is described -in \k{faq-openssh-bad-openssl} in the FAQ. +size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel. \H{errors-x11-proxy} \q{PuTTY X11 proxy: \e{various errors}} diff --git a/doc/faq.but b/doc/faq.but index 42f965b2..4d9e14a9 100644 --- a/doc/faq.but +++ b/doc/faq.but @@ -27,18 +27,18 @@ else. \I{supported features}In general, if you want to know if PuTTY supports a particular feature, you should look for it on the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}. +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}. In particular: \b try the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes page}, and see if you can find the feature on there. If a feature is listed there, it's been implemented. If it's listed as a change made \e{since} the latest version, it should be available in the development snapshots, in which case testing will be very welcome. \b try the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist page}, and see if you can find the feature there. If it's on there, and not in the \q{Recently fixed} section, it probably \e{hasn't} been implemented. @@ -54,7 +54,7 @@ version 0.52. \cw{ssh.com} SSH-2 private key files? PuTTY doesn't support this natively (see -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry} for reasons why not), but as of 0.53 PuTTYgen can convert both OpenSSH and \cw{ssh.com} private key files into PuTTY's format. @@ -236,7 +236,7 @@ port, or any other port of PuTTY, they were mistaken. We don't. There are some third-party ports to various platforms, mentioned on the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}. +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}. \S{faq-unix}{Question} \I{Unix version}Is there a port to Unix? @@ -323,7 +323,7 @@ for, it might be a long time before any of us get round to learning a new system and doing the port for that. However, some of the work has been done by other people; see the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website} for various third-party ports. \S{faq-iphone}{Question} Will there be a port to the iPhone? @@ -351,7 +351,7 @@ Most of the code cleanup work would be a good thing to happen in general, so if anyone feels like helping, we wouldn't say no. See also -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}. +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}. \S{faq-vb}{Question} Is the SSH or Telnet code available as a Visual Basic component? @@ -601,34 +601,6 @@ documentation.) \H{faq-trouble} Troubleshooting -\S{faq-incorrect-mac}{Question} Why do I see \q{Incorrect MAC -received on packet}? - -One possible cause of this that used to be common is a bug in old -SSH-2 servers distributed by \cw{ssh.com}. (This is not the only -possible cause; see \k{errors-crc} in the documentation.) -Version 2.3.0 and below of their SSH-2 server -constructs Message Authentication Codes in the wrong way, and -expects the client to construct them in the same wrong way. PuTTY -constructs the MACs correctly by default, and hence these old -servers will fail to work with it. - -If you are using PuTTY version 0.52 or better, this should work -automatically: PuTTY should detect the buggy servers from their -version number announcement, and automatically start to construct -its MACs in the same incorrect manner as they do, so it will be able -to work with them. - -If you are using PuTTY version 0.51 or below, you can enable the -workaround by going to the SSH panel and ticking the box labelled -\q{Imitate SSH2 MAC bug}. It's possible that you might have to do -this with 0.52 as well, if a buggy server exists that PuTTY doesn't -know about. - -In this context MAC stands for \ii{Message Authentication Code}. It's a -cryptographic term, and it has nothing at all to do with Ethernet -MAC (Media Access Control) addresses. - \S{faq-pscp-protocol}{Question} Why do I see \q{Fatal: Protocol error: Expected control record} in PSCP? @@ -664,21 +636,6 @@ Clicking on \q{ANSI Green} won't turn your session green; it will only allow you to adjust the \e{shade} of green used when PuTTY is instructed by the server to display green text. -\S{faq-winsock2}{Question} Plink on \i{Windows 95} says it can't find -\i\cw{WS2_32.DLL}. - -Plink requires the extended Windows network library, WinSock version -2. This is installed as standard on Windows 98 and above, and on -Windows NT, and even on later versions of Windows 95; but early -Win95 installations don't have it. - -In order to use Plink on these systems, you will need to download -the -\W{http://www.microsoft.com/windows95/downloads/contents/wuadmintools/s_wunetworkingtools/w95sockets2/}{WinSock 2 upgrade}: - -\c http://www.microsoft.com/windows95/downloads/contents/ -\c wuadmintools/s_wunetworkingtools/w95sockets2/ - \S{faq-outofmem}{Question} After trying to establish an SSH-2 connection, PuTTY says \q{\ii{Out of memory}} and dies. @@ -891,45 +848,10 @@ us \q{I wanted the F1 key to send \c{^[[11~}, but instead it's sending \c{^[OP}, can this be done?}, or something similar. You should still read the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback page} on the PuTTY website (also provided as \k{feedback} in the manual), and follow the guidelines contained in that. -\S{faq-openssh-bad-openssl}{Question} Since my SSH server was upgraded -to \i{OpenSSH} 3.1p1/3.4p1, I can no longer connect with PuTTY. - -There is a known problem when OpenSSH has been built against an -incorrect version of OpenSSL; the quick workaround is to configure -PuTTY to use SSH protocol 2 and the Blowfish cipher. - -For more details and OpenSSH patches, see -\W{http://bugzilla.mindrot.org/show_bug.cgi?id=138}{bug 138} in the -OpenSSH BTS. - -This is not a PuTTY-specific problem; if you try to connect with -another client you'll likely have similar problems. (Although PuTTY's -default cipher differs from many other clients.) - -\e{OpenSSH 3.1p1:} configurations known to be broken (and symptoms): - -\b SSH-2 with AES cipher (PuTTY says \q{Assertion failed! Expression: -(len & 15) == 0} in \cw{sshaes.c}, or \q{Out of memory}, or crashes) - -\b SSH-2 with 3DES (PuTTY says \q{Incorrect MAC received on packet}) - -\b SSH-1 with Blowfish (PuTTY says \q{Incorrect CRC received on -packet}) - -\b SSH-1 with 3DES - -\e{OpenSSH 3.4p1:} as of 3.4p1, only the problem with SSH-1 and -Blowfish remains. Rebuild your server, apply the patch linked to from -bug 138 above, or use another cipher (e.g., 3DES) instead. - -\e{Other versions:} we occasionally get reports of the same symptom -and workarounds with older versions of OpenSSH, although it's not -clear the underlying cause is the same. - \S{faq-ssh2key-ssh1conn}{Question} Why do I see \q{Couldn't load private key from ...}? Why can PuTTYgen load my key but not PuTTY? @@ -1060,7 +982,7 @@ still. We do not recommend it.) This is caused by a bug in certain versions of \i{Windows XP} which is triggered by PuTTY 0.58. This was fixed in 0.59. The -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} entry in PuTTY's wishlist has more details. \S{faq-system32}{Question} When I put 32-bit PuTTY in @@ -1080,6 +1002,26 @@ appropriate kind of binaries in \cw{SYSTEM32}. Thus, operations in the PuTTY suite that involve it accessing its own executables, such as \i{\q{New Session}} and \q{Duplicate Session}, will not work. +\S{faq-iutf8}{Question} After I upgraded PuTTY to 0.68, I can no longer +connect to my embedded device or appliance. + +If your SSH server has started unexpectedly closing SSH connections +after you enter your password, and it worked before 0.68, you may have +a buggy server that objects to certain SSH protocol extensions. + +The SSH protocol recently gained a new \q{terminal mode}, \cw{IUTF8}, +which PuTTY sends by default; see \k{config-ttymodes}. This is the +first new terminal mode since the SSH-2 protocol was defined. While +servers are supposed to ignore modes they don't know about, some buggy +servers will unceremoniously close the connection if they see anything +they don't recognise. SSH servers in embedded devices, network +appliances, and the like seem to disproportionately have this bug. + +If you think you have such a server, from 0.69 onwards you can disable +sending of the \cw{IUTF8} mode: on the SSH / TTY panel, select +\cw{IUTF8} on the list, select \q{Nothing}, and press \q{Set}. (It's +not possible to disable sending this mode in 0.68.) + \H{faq-secure} Security questions \S{faq-publicpc}{Question} Is it safe for me to download PuTTY and diff --git a/doc/feedback.but b/doc/feedback.but index e0854fc5..b8428e41 100644 --- a/doc/feedback.but +++ b/doc/feedback.but @@ -112,7 +112,7 @@ If you think you have found a bug in PuTTY, your first steps should be: \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist page} on the PuTTY website, and see if we already know about the problem. If we do, it is almost certainly not necessary to mail us about it, unless you think you have extra information that might be @@ -121,12 +121,12 @@ specific extra information about a particular bug, the Wishlist page will say so.) \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change Log} on the PuTTY website, and see if we have already fixed the bug in the \i{development snapshots}. \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ} on the PuTTY website (also provided as \k{faq} in the manual), and see if it answers your question. The FAQ lists the most common things which people think are bugs, but which aren't bugs. @@ -188,7 +188,7 @@ you haven't supplied us with full information about the actual bug, then we won't be able to find a better solution. \b -\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html} +\W{https://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{https://www.chiark.greenend.org.uk/~sgtatham/bugs.html} is an article on how to report bugs effectively in general. If your bug report is \e{particularly} unclear, we may ask you to go away, read this article, and then report the bug again. @@ -224,14 +224,14 @@ If you want to request a new feature in PuTTY, the very first things you should do are: \b Check the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist page} on the PuTTY website, and see if your feature is already on the list. If it is, it probably won't achieve very much to repeat the request. (But see \k{feedback-feature-priority} if you want to persuade us to give your particular feature higher priority.) \b Check the Wishlist and -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change Log} on the PuTTY website, and see if we have already added your feature in the development snapshots. If it isn't clear, download the latest development snapshot and see if the feature is present. @@ -350,7 +350,7 @@ Of course, if the web site has some other error (Connection Refused, If you want to report a problem with our web site, check that you're looking at our \e{real} web site and not a mirror. The real web site is at -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{http://www.chiark.greenend.org.uk/~sgtatham/putty/}; +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{https://www.chiark.greenend.org.uk/~sgtatham/putty/}; if that's not where you're reading this, then don't report the problem to us until you've checked that it's really a problem with the main site. If it's only a problem with the mirror, you should @@ -399,7 +399,7 @@ setting up a mirror. You already have permission. If the mirror is in a country where we don't already have plenty of mirrors, we may be willing to add it to the list on our -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors page}. Read the guidelines on that page, make sure your mirror works, and email us the information listed at the bottom of the page. @@ -414,7 +414,7 @@ to be a cheap way to gain search rankings. If you have technical questions about the process of mirroring, then you might want to mail us before setting up the mirror (see also the -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page}); +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page}); but if you just want to ask for permission, you don't need to. You already have permission. diff --git a/doc/index.but b/doc/index.but index 1e71234f..debd2ab2 100644 --- a/doc/index.but +++ b/doc/index.but @@ -73,7 +73,7 @@ from SSH and Telnet \IM{three-button mouse} mouse, three-button \IM{left mouse button}{left button} left mouse button -\IM{middle mouse button}{middle button} middle mouse button +\IM{middle mouse button}{middle button}{middle-clicking} middle mouse button \IM{right mouse button}{right button} right mouse button \IM{selecting words}{word-by-word selection} selecting whole words @@ -92,6 +92,18 @@ from SSH and Telnet \IM{right mouse button, with Ctrl} right mouse button, with Ctrl \IM{right mouse button, with Ctrl} Ctrl, with right mouse button +\IM{selections} selections, multiple +\IM{selections} clipboards, multiple + +\IM{PRIMARY} \c{PRIMARY} selection +\IM{PRIMARY} selection, \c{PRIMARY} + +\IM{CLIPBOARD selection} \c{CLIPBOARD} selection +\IM{CLIPBOARD selection} selection, \c{CLIPBOARD} + +\IM{SECONDARY} \c{SECONDARY} selection +\IM{SECONDARY} selection, \c{SECONDARY} + \IM{system menu} system menu \IM{system menu} menu, system \IM{system menu} window menu @@ -340,6 +352,12 @@ saved sessions from \IM{remote-controlled printing} ANSI printing \IM{remote-controlled printing} remote-controlled printing \IM{remote-controlled printing} printing, remote-controlled +\IM{remote-controlled printing} passthrough printing + +\IM{Control-H} Control-H +\IM{Control-H} Ctrl-H +\IM{Control-?} Control-? +\IM{Control-?} Ctrl-? \IM{Home and End keys} Home key \IM{Home and End keys} End key @@ -829,9 +847,6 @@ saved sessions from \IM{login scripts}{startup scripts} login scripts \IM{login scripts}{startup scripts} startup scripts -\IM{WS2_32.DLL} \cw{WS2_32.DLL} -\IM{WS2_32.DLL} WinSock version 2 - \IM{Red Hat Linux} Red Hat Linux \IM{Red Hat Linux} Linux, Red Hat @@ -859,6 +874,9 @@ saved sessions from \IM{GSSAPI credential delegation} credential delegation, GSSAPI \IM{GSSAPI credential delegation} delegation, of GSSAPI credentials +\IM{cascading credentials} cascading credentials +\IM{cascading credentials} credentials, cascading + \IM{SYSTEM32} \cw{SYSTEM32} directory, on Windows \IM{32-bit Windows} 32-bit Windows diff --git a/doc/man-pl.but b/doc/man-pl.but index a46e6a19..58ca7a28 100644 --- a/doc/man-pl.but +++ b/doc/man-pl.but @@ -182,6 +182,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{\-noshare} + +\dd Don't test and try to share an existing connection, always make +a new connection. + +\dt \cw{\-share} + +\dd Test and try to share an existing connection. + \dt \cw{\-hostkey} \e{key} \dd Specify an acceptable host public key. This option may be specified @@ -260,7 +269,7 @@ exists, nonzero otherwise. For more information on plink, it's probably best to go and look at the manual on the PuTTY web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{plink-manpage-bugs} BUGS diff --git a/doc/man-pscp.but b/doc/man-pscp.but index 05e5a23c..6c703e13 100644 --- a/doc/man-pscp.but +++ b/doc/man-pscp.but @@ -174,7 +174,7 @@ encrypted packet data. For more information on \cw{pscp} it's probably best to go and look at the manual on the PuTTY web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{pscp-manpage-bugs} BUGS diff --git a/doc/man-psft.but b/doc/man-psft.but index 80d86ecf..51f30d3a 100644 --- a/doc/man-psft.but +++ b/doc/man-psft.but @@ -159,7 +159,7 @@ at the \cw{psftp>} prompt. For more information on \cw{psftp} it's probably best to go and look at the manual on the PuTTY web page: -\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{psftp-manpage-bugs} BUGS diff --git a/doc/man-ptel.but b/doc/man-ptel.but index a3b79405..73b85ecc 100644 --- a/doc/man-ptel.but +++ b/doc/man-ptel.but @@ -208,7 +208,7 @@ your home directory. For more information on PuTTY and PuTTYtel, it's probably best to go and look at the manual on the web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{puttytel-manpage-bugs} BUGS diff --git a/doc/man-putt.but b/doc/man-putt.but index df7b9e1f..cb7cca47 100644 --- a/doc/man-putt.but +++ b/doc/man-putt.but @@ -321,7 +321,7 @@ your home directory. For more information on PuTTY, it's probably best to go and look at the manual on the web page: -\W{http://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/putty/} +\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} \S{putty-manpage-bugs} BUGS diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but index 9ec90066..85008c88 100644 --- a/doc/pgpkeys.but +++ b/doc/pgpkeys.but @@ -53,31 +53,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{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key}} +\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{4096R/04676F7C} (long version: -\cw{4096R/AB585DC604676F7C}). Fingerprint: -\cw{440D\_E3B5\_B7A1\_CA85\_B3CC\_\_1718\_AB58\_5DC6\_0467\_6F7C} +\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint: +\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)} -\dd RSA, 2048-bit. Key ID: \cw{2048R/B43434E4} (long version: -\cw{2048R/9DFE2648B43434E4}). Fingerprint: -\cw{0054\_DDAA\_8ADA\_15D2\_768A\_\_6DE7\_9DFE\_2648\_B434\_34E4} +\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint: +\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)} -\dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version: -\cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID: -\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}). -Fingerprint: -\cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B} +\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint: +\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key}} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)} -\dd RSA, 2048-bit. Key ID: \cw{2048R/D15F7E8A} (long version: -\cw{2048R/EEF20295D15F7E8A}). Fingerprint: -\cw{0A3B\_0048\_FE49\_9B67\_A234\_\_FEB6\_EEF2\_0295\_D15F\_7E8A} +\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint: +\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98} \H{pgpkeys-security} Security details @@ -156,60 +150,85 @@ once. \H{pgpkeys-rollover} Key rollover -Our current keys were generated in September 2015, except for the -Secure Contact Key which was generated in February 2016 (we didn't -think of it until later). +Our current keys were generated in August 2018. + +Each new Master Key is signed with the old one, to show that it really +is owned by the same people and not substituted by an attacker. -Prior to that, we had a much older set of keys generated in 2000. For -each of the key types above (other than the Secure Contact Key), we -provided both an RSA key \e{and} a DSA key (because at the time we -generated them, RSA was not in practice available to everyone, due to -export restrictions). +Each new Master Key also signs the previous Release Keys, in case +you're trying to verify the signatures on a release prior to the +rollover and can find a chain of trust to those keys from any of the +people who have signed our new Master Key. -The new Master Key is signed with both of the old ones, to show that -it really is owned by the same people and not substituted by an -attacker. Also, we have retrospectively signed the old Release Keys -with the new Master Key, in case you're trying to verify the -signatures on a release prior to the rollover and can find a chain of -trust to those keys from any of the people who have signed our new -Master Key. +Each release is signed with the Release Key that was current at the +time of release. We don't go back and re-sign old releases with newly +generated keys. -Future releases will be signed with the up-to-date keys shown above. -Releases prior to the rollover are signed with the old Release Keys. +The details of all previous keys are given here. + +\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)} + +\dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version: +\cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID: +\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}). +Fingerprint: +\cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B} + +\s{Keys generated in the 2015 rollover} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key} (2015)} + +\dd RSA, 4096-bit. Key ID: \cw{4096R/04676F7C} (long version: +\cw{4096R/AB585DC604676F7C}). Fingerprint: +\cw{440D\_E3B5\_B7A1\_CA85\_B3CC\_\_1718\_AB58\_5DC6\_0467\_6F7C} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key} (2015)} + +\dd RSA, 2048-bit. Key ID: \cw{2048R/B43434E4} (long version: +\cw{2048R/9DFE2648B43434E4}). Fingerprint: +\cw{0054\_DDAA\_8ADA\_15D2\_768A\_\_6DE7\_9DFE\_2648\_B434\_34E4} + +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key} (2015)} + +\dd RSA, 2048-bit. Key ID: \cw{2048R/D15F7E8A} (long version: +\cw{2048R/EEF20295D15F7E8A}). Fingerprint: +\cw{0A3B\_0048\_FE49\_9B67\_A234\_\_FEB6\_EEF2\_0295\_D15F\_7E8A} -For completeness, those old keys are given here: +\s{Original keys generated in 2000} (two sets, RSA and DSA) -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)} \dd RSA, 1024-bit. Key ID: \cw{1024R/1E34AC41} (long version: \cw{1024R/9D5877BF1E34AC41}). Fingerprint: \cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{\s{Master Key} (original DSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{\s{Master Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/6A93B34E} (long version: \cw{1024D/4F5E6DF56A93B34E}). Fingerprint: \cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original RSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original RSA)} \dd RSA, 1024-bit. Key ID: \cw{1024R/B41CAE29} (long version: \cw{1024R/EF39CCC0B41CAE29}). Fingerprint: \cw{AE\_65\_D3\_F7\_85\_D3\_18\_E0\_\_3B\_0C\_9B\_02\_FF\_3A\_81\_FE} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{\s{Release Key} (original DSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{\s{Release Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/08B0A90B} (long version: \cw{1024D/FECD6F3F08B0A90B}). Fingerprint: \cw{00B1\_1009\_38E6\_9800\_6518\_\_F0AB\_FECD\_6F3F\_08B0\_A90B} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original RSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original RSA)} \dd RSA, 1024-bit. Key ID: \cw{1024R/32B903A9} (long version: \cw{1024R/FAAED21532B903A9}). Fingerprint: \cw{86\_8B\_1F\_79\_9C\_F4\_7F\_BD\_\_8B\_1B\_D7\_8E\_C6\_4E\_4C\_03} -\dt \W{http://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{\s{Snapshot Key} (original DSA)} +\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{\s{Snapshot Key} (original DSA)} \dd DSA, 1024-bit. Key ID: \cw{1024D/7D3E4A00} (long version: \cw{1024D/165E56F77D3E4A00}). Fingerprint: diff --git a/doc/plink.but b/doc/plink.but index 351e13ea..74da18a1 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -41,7 +41,7 @@ use Plink: \c Z:\sysosd>plink \c Plink: command-line connection utility -\c Release 0.68 +\c Release 0.70 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: @@ -75,6 +75,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 -noshare disable use of connection sharing +\c -share enable use of connection sharing \c -hostkey aa:bb:cc:... \c manually specify a host key (may be repeated) \c -m file read remote command(s) from file @@ -237,6 +239,28 @@ line. (This option is only meaningful with the SSH-2 protocol.) +\S2{plink-option-share} \I{-share-plink}\c{-share}: +Test and try to share an existing connection. + +This option tris to detect if an existing connection can be shared +(See \k{config-ssh-sharing} for more information about SSH connection +sharing.) and reuses that connection. + +A Plink invocation of the form: + +\c plink -share +\e iiiiiiiii + +will test whether there is currently a viable \q{upstream} for the +session in question, which can be specified using any syntax you'd +normally use with Plink to make an actual connection (a host/port +number, a bare saved session name, \c{-load}, etc). If no \q{upstream} +viable session is found and \c{-share} is specified, this connection +will be become the \q{upstream} connection for subsequent connection +sharing tries. + +(This option is only meaningful with the SSH-2 protocol.) + \S2{plink-option-shareexists} \I{-shareexists-plink}\c{-shareexists}: test for connection-sharing upstream diff --git a/doc/pscp.but b/doc/pscp.but index 27643a46..7b90810b 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -39,7 +39,7 @@ use PSCP: \c Z:\owendadmin>pscp \c PuTTY Secure Copy client -\c Release 0.68 +\c Release 0.70 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec diff --git a/doc/udp.but b/doc/udp.but index c50464ee..b71688b7 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -138,9 +138,9 @@ construct. Use these wherever possible. \H{udp-multi-compiler} Independence of specific compiler -Windows PuTTY can currently be compiled with any of four Windows -compilers: MS Visual C, Borland's freely downloadable C compiler, -the Cygwin / \cw{mingw32} GNU tools, and \cw{lcc-win32}. +Windows PuTTY can currently be compiled with any of three Windows +compilers: MS Visual C, the Cygwin / \cw{mingw32} GNU tools, and +\cw{clang} (in MS compatibility mode). This is a really useful property of PuTTY, because it means people who want to contribute to the coding don't depend on having a @@ -331,7 +331,7 @@ local state structures \c{s} or \c{st} in each function, or the backend-wide structure \c{ssh}. See -\W{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html} +\W{https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html} for a more in-depth discussion of what these macros are for and how they work. diff --git a/doc/using.but b/doc/using.but index 7d184b7c..515e3a4d 100644 --- a/doc/using.but +++ b/doc/using.but @@ -21,27 +21,31 @@ the \I{Windows clipboard}Windows \i{clipboard}, so that you can paste (for example) URLs into a web browser, or paste from a word processor or spreadsheet into your terminal session. -PuTTY's copy and paste works entirely with the \i{mouse}. In order -to copy text to the clipboard, you just click the \i{left mouse -button} in the \i{terminal window}, and drag to \I{selecting text}select -text. When you let go of the button, the text is \e{automatically} -copied to the clipboard. You do not need to press Ctrl-C or -Ctrl-Ins; in fact, if you do press Ctrl-C, PuTTY will send a Ctrl-C -character down your session to the server where it will probably -cause a process to be interrupted. - -Pasting is done using the right button (or the middle mouse button, -if you have a \i{three-button mouse} and have set it up; see +By default, PuTTY's copy and paste works entirely with the \i{mouse}. +(This will be familiar to people who have used \i\c{xterm} on Unix.) +In order to copy text to the clipboard, you just click the \i{left +mouse button} in the \i{terminal window}, and drag to +\I{selecting text}select text. When you let go of the button, the text +is \e{automatically} copied to the clipboard. You do not need to press +\i{Ctrl-C} or \i{Ctrl-Ins}; in fact, if you do press Ctrl-C, PuTTY will +send a Ctrl-C character down your session to the server where it will +probably cause a process to be interrupted. + +Pasting into PuTTY is done using the right button (or the middle mouse +button, if you have a \i{three-button mouse} and have set it up; see \k{config-mouse}). (Pressing \i{Shift-Ins}, or selecting \q{Paste} from the \I{right mouse button, with Ctrl}Ctrl+right-click \i{context menu}, have the same effect.) When you click the \i{right mouse button}, PuTTY will read whatever is in -the Windows clipboard and paste it into your session, \e{exactly} as -if it had been typed at the keyboard. (Therefore, be careful of -pasting formatted text into an editor that does automatic indenting; -you may find that the spaces pasted from the clipboard plus the -spaces added by the editor add up to too many spaces and ruin the -formatting. There is nothing PuTTY can do about this.) +the Windows clipboard and paste it into your session. By default, this +behaves \e{exactly} as if the clipboard contents had been typed at the +keyboard; therefore, be careful of pasting formatted text into an +editor that does automatic \i{indenting}, as you may find that the spaces +pasted from the clipboard plus the spaces added by the editor add up +to too many spaces and ruin the formatting. (Some remote applications +can ask PuTTY to identify text that is being pasted, to avoid this +sort of problem; but if your application does not, there is nothing +PuTTY can do to avoid this.) If you \i{double-click} the left mouse button, PuTTY will \I{selecting words}select a whole word. If you double-click, hold @@ -69,6 +73,15 @@ middle mouse button to paste, then the right mouse button does this instead.) Click the button on the screen, and you can pick up the nearest end of the selection and drag it to somewhere else. +If you are running PuTTY itself on Unix (not just using it to connect +to a Unix system from Windows), by default you will likely have to use +similar mouse actions in other applications to paste the text you +copied from PuTTY, and to copy text for pasting into PuTTY; actions +like \i{Ctrl-C} and Ctrl-V will likely not behave as you expect. +\K{config-clipboards} explains why this is, and how you can change the +behaviour. (On Windows there is only a single selection shared with other +applications, so this confusion does not arise.) + It's possible for the server to ask to \I{mouse reporting}handle mouse clicks in the PuTTY window itself. If this happens, the \i{mouse pointer} will turn into an arrow, and using the mouse to copy and paste will only @@ -76,6 +89,9 @@ work if you hold down Shift. See \k{config-features-mouse} and \k{config-mouseshift} for details of this feature and how to configure it. +You can customise much of this behaviour, for instance to enable copy +and paste from the keyboard; see \k{config-selection}. + \S{using-scrollback} \I{scrollback}Scrolling the screen back PuTTY keeps track of text that has scrolled up off the top of the @@ -86,8 +102,10 @@ window to look back up the session \i{history} and find it again. As well as using the scrollbar, you can also page the scrollback up and down by pressing \i{Shift-PgUp} and \i{Shift-PgDn}. You can -scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}. These -are still available if you configure the scrollbar to be invisible. +scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}, or +to the top/bottom of the scrollback with \i{Ctrl-Shift-PgUp} and +\i{Ctrl-Shift-PgDn}. These are still available if you configure the +scrollbar to be invisible. By default the last 2000 lines scrolled off the top are preserved for you to look at. You can increase (or decrease) this @@ -1042,3 +1060,15 @@ any processes started with Duplicate Session, New Session etc. (However, if you're invoking PuTTY tools explicitly, for instance as a proxy command, you'll need to arrange to pass them the \c{-restrict-acl} option yourself, if that's what you want.) + +If Pageant is started with the \c{-restrict-acl} option, and you use +it to launch a PuTTY session from its System Tray submenu, then +Pageant will \e{not} default to starting the PuTTY subprocess with a +restricted ACL. This is because PuTTY is more likely to suffer reduced +functionality as a result of restricted ACLs (e.g. screen reader +software will have a greater need to interact with it), whereas +Pageant stores the more critical information (hence benefits more from +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 +\c{-restrict-putty-acl} option. diff --git a/errsock.c b/errsock.c index 854fd8be..9ab7c3db 100644 --- a/errsock.c +++ b/errsock.c @@ -5,69 +5,74 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "tree234.h" #include "putty.h" #include "network.h" -typedef struct Socket_error_tag *Error_Socket; - -struct Socket_error_tag { - const struct socket_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - +typedef struct { char *error; - Plug plug; -}; + Plug *plug; + + Socket sock; +} ErrorSocket; -static Plug sk_error_plug(Socket s, Plug p) +static Plug *sk_error_plug(Socket *s, Plug *p) { - Error_Socket ps = (Error_Socket) s; - Plug ret = ps->plug; + ErrorSocket *es = container_of(s, ErrorSocket, sock); + Plug *ret = es->plug; if (p) - ps->plug = p; + es->plug = p; return ret; } -static void sk_error_close(Socket s) +static void sk_error_close(Socket *s) { - Error_Socket ps = (Error_Socket) s; + ErrorSocket *es = container_of(s, ErrorSocket, sock); - sfree(ps->error); - sfree(ps); + sfree(es->error); + sfree(es); } -static const char *sk_error_socket_error(Socket s) +static const char *sk_error_socket_error(Socket *s) { - Error_Socket ps = (Error_Socket) s; - return ps->error; + ErrorSocket *es = container_of(s, ErrorSocket, sock); + return es->error; } -static char *sk_error_peer_info(Socket s) +static SocketPeerInfo *sk_error_peer_info(Socket *s) { return NULL; } -Socket new_error_socket(const char *errmsg, Plug plug) +static const SocketVtable ErrorSocket_sockvt = { + sk_error_plug, + sk_error_close, + NULL /* write */, + NULL /* write_oob */, + NULL /* write_eof */, + NULL /* flush */, + NULL /* set_frozen */, + sk_error_socket_error, + sk_error_peer_info, +}; + +static Socket *new_error_socket_internal(char *errmsg, Plug *plug) { - static const struct socket_function_table socket_fn_table = { - sk_error_plug, - sk_error_close, - NULL /* write */, - NULL /* write_oob */, - NULL /* write_eof */, - NULL /* flush */, - NULL /* set_frozen */, - sk_error_socket_error, - sk_error_peer_info, - }; + ErrorSocket *es = snew(ErrorSocket); + es->sock.vt = &ErrorSocket_sockvt; + es->plug = plug; + es->error = errmsg; + return &es->sock; +} - Error_Socket ret; +Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...) +{ + va_list ap; + char *msg; - ret = snew(struct Socket_error_tag); - ret->fn = &socket_fn_table; - ret->plug = plug; - ret->error = dupstr(errmsg); + va_start(ap, fmt); + msg = dupvprintf(fmt, ap); + va_end(ap); - return (Socket) ret; + return new_error_socket_internal(msg, plug); } diff --git a/fuzzterm.c b/fuzzterm.c index 15b5d635..83a176e6 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -7,7 +7,9 @@ #include "terminal.h" /* For Unix in particular, but harmless if this main() is reused elsewhere */ -const int buildinfo_gtk_relevant = FALSE; +const bool buildinfo_gtk_relevant = false; + +static const TermWinVtable fuzz_termwin_vt; int main(int argc, char **argv) { @@ -16,14 +18,17 @@ int main(int argc, char **argv) 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_int(conf, CONF_utf8_override), + conf_get_bool(conf, CONF_utf8_override), CS_NONE, conf_get_int(conf, CONF_vtmode)); - term = term_init(conf, &ucsdata, NULL); + 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. */ @@ -32,20 +37,17 @@ int main(int argc, char **argv) #endif while (!feof(stdin)) { len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, 0, blk, len); + term_data(term, false, blk, len); } term_update(term); return 0; } -int from_backend(void *frontend, int is_stderr, const char *data, int len) -{ return 0; } - /* functions required by terminal.c */ - -void request_resize(void *frontend, int x, int y) { } -void do_text(Context ctx, int x, int y, wchar_t * text, int len, - unsigned long attr, int lattr) +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; @@ -55,8 +57,9 @@ void do_text(Context ctx, int x, int y, wchar_t * text, int len, } printf("\n"); } -void do_cursor(Context ctx, int x, int y, wchar_t * text, int len, - unsigned long attr, int lattr) +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; @@ -66,42 +69,69 @@ void do_cursor(Context ctx, int x, int y, wchar_t * text, int len, } printf("\n"); } -int char_width(Context ctx, int uc) { return 1; } -void set_title(void *frontend, char *t) { } -void set_icon(void *frontend, char *t) { } -void set_sbar(void *frontend, int a, int b, int c) { } - -void ldisc_send(void *handle, const char *buf, int len, int interactive) {} -void ldisc_echoedit_update(void *handle) {} -Context get_ctx(void *frontend) { - static char x; - - return &x; -} -void free_ctx(Context ctx) { } -void palette_set(void *frontend, int a, int b, int c, int d) { } -void palette_reset(void *frontend) { } -void write_clip(void *frontend, wchar_t *a, int *b, int c, int d) { } -void get_clip(void *frontend, wchar_t **w, int *i) { } -void set_raw_mouse_mode(void *frontend, int m) { } -void request_paste(void *frontend) { } -void do_beep(void *frontend, int a) { } -void sys_cursor(void *frontend, int x, int y) { } -void fatalbox(const char *fmt, ...) { exit(0); } +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) {} +static void fuzz_set_icon_title(TermWin *tw, const char *icontitle) {} +static void fuzz_set_minimised(TermWin *tw, bool minimised) {} +static bool fuzz_is_minimised(TermWin *tw) { return false; } +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 bool fuzz_palette_get(TermWin *tw, int n, int *r, int *g, int *b) +{ return false; } +static void fuzz_palette_set(TermWin *tw, int n, int r, int g, int b) {} +static void fuzz_palette_reset(TermWin *tw) {} +static void fuzz_get_pos(TermWin *tw, int *x, int *y) { *x = *y = 0; } +static void fuzz_get_pixels(TermWin *tw, int *x, int *y) { *x = *y = 0; } +static const char *fuzz_get_title(TermWin *tw, bool icon) { return "moo"; } +static bool fuzz_is_utf8(TermWin *tw) { return true; } + +static const TermWinVtable fuzz_termwin_vt = { + fuzz_setup_draw_ctx, + fuzz_draw_text, + fuzz_draw_cursor, + fuzz_char_width, + fuzz_free_draw_ctx, + fuzz_set_cursor_pos, + fuzz_set_raw_mouse_mode, + fuzz_set_scrollbar, + fuzz_bell, + fuzz_clip_write, + fuzz_clip_request_paste, + fuzz_refresh, + fuzz_request_resize, + fuzz_set_title, + fuzz_set_icon_title, + fuzz_set_minimised, + fuzz_is_minimised, + fuzz_set_maximised, + fuzz_move, + fuzz_set_zorder, + fuzz_palette_get, + fuzz_palette_set, + fuzz_palette_reset, + fuzz_get_pos, + fuzz_get_pixels, + fuzz_get_title, + fuzz_is_utf8, +}; + +void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} +void ldisc_echoedit_update(Ldisc *ldisc) {} void modalfatalbox(const char *fmt, ...) { exit(0); } void nonfatal(const char *fmt, ...) { } -void set_iconic(void *frontend, int iconic) { } -void move_window(void *frontend, int x, int y) { } -void set_zorder(void *frontend, int top) { } -void refresh_window(void *frontend) { } -void set_zoomed(void *frontend, int zoomed) { } -int is_iconic(void *frontend) { return 0; } -void get_window_pos(void *frontend, int *x, int *y) { *x = 0; *y = 0; } -void get_window_pixels(void *frontend, int *x, int *y) { *x = 0; *y = 0; } -char *get_window_title(void *frontend, int icon) { return "moo"; } -int frontend_is_utf8(void *frontend) { return TRUE; } - /* needed by timing.c */ void timer_change_notify(unsigned long next) { } @@ -137,15 +167,10 @@ void dlg_error_msg(void *dlg, const char *msg) { } void dlg_end(void *dlg, int value) { } void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) { } -int dlg_coloursel_results(union control *ctrl, void *dlg, - int *r, int *g, int *b) { return 0; } +bool dlg_coloursel_results(union control *ctrl, void *dlg, + int *r, int *g, int *b) { return false; } void dlg_refresh(union control *ctrl, void *dlg) { } -/* miscellany */ -void logevent(void *frontend, const char *msg) { } -int askappend(void *frontend, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) { return 0; } - const char *const appname = "FuZZterm"; const int ngsslibs = 0; const char *const gsslibnames[0] = { }; @@ -163,6 +188,11 @@ 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; @@ -185,5 +215,3 @@ char *x_get_default(const char *key) { return NULL; /* this is a stub */ } - - diff --git a/import.c b/import.c index adf68777..e3ba53c4 100644 --- a/import.c +++ b/import.c @@ -12,39 +12,37 @@ #include "ssh.h" #include "misc.h" -int openssh_pem_encrypted(const Filename *filename); -int openssh_new_encrypted(const Filename *filename); -struct ssh2_userkey *openssh_pem_read(const Filename *filename, - char *passphrase, - const char **errmsg_p); -struct ssh2_userkey *openssh_new_read(const Filename *filename, - char *passphrase, - const char **errmsg_p); -int openssh_auto_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase); -int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase); -int openssh_new_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase); - -int sshcom_encrypted(const Filename *filename, char **comment); -struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, - const char **errmsg_p); -int sshcom_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase); +static bool openssh_pem_encrypted(const Filename *file); +static bool openssh_new_encrypted(const Filename *file); +static struct ssh2_userkey *openssh_pem_read( + const Filename *file, const char *passphrase, const char **errmsg_p); +static struct ssh2_userkey *openssh_new_read( + const Filename *file, const char *passphrase, const char **errmsg_p); +static bool openssh_auto_write( + const Filename *file, struct ssh2_userkey *key, const char *passphrase); +static bool openssh_pem_write( + const Filename *file, struct ssh2_userkey *key, const char *passphrase); +static bool openssh_new_write( + const Filename *file, struct ssh2_userkey *key, const char *passphrase); + +static bool sshcom_encrypted(const Filename *file, char **comment); +static struct ssh2_userkey *sshcom_read( + const Filename *file, const char *passphrase, const char **errmsg_p); +static bool sshcom_write( + const Filename *file, struct ssh2_userkey *key, const char *passphrase); /* * Given a key type, determine whether we know how to import it. */ -int import_possible(int type) +bool import_possible(int type) { if (type == SSH_KEYTYPE_OPENSSH_PEM) - return 1; + return true; if (type == SSH_KEYTYPE_OPENSSH_NEW) - return 1; + return true; if (type == SSH_KEYTYPE_SSHCOM) - return 1; - return 0; + return true; + return false; } /* @@ -63,7 +61,7 @@ int import_target_type(int type) /* * Determine whether a foreign key is encrypted. */ -int import_encrypted(const Filename *filename, int type, char **comment) +bool import_encrypted(const Filename *filename, int type, char **comment) { if (type == SSH_KEYTYPE_OPENSSH_PEM) { /* OpenSSH PEM format doesn't contain a key comment at all */ @@ -77,7 +75,7 @@ int import_encrypted(const Filename *filename, int type, char **comment) } else if (type == SSH_KEYTYPE_SSHCOM) { return sshcom_encrypted(filename, comment); } - return 0; + return false; } /* @@ -107,17 +105,17 @@ struct ssh2_userkey *import_ssh2(const Filename *filename, int type, /* * Export an SSH-1 key. */ -int export_ssh1(const Filename *filename, int type, struct RSAKey *key, - char *passphrase) +bool export_ssh1(const Filename *filename, int type, struct RSAKey *key, + char *passphrase) { - return 0; + return false; } /* * Export an SSH-2 key. */ -int export_ssh2(const Filename *filename, int type, - struct ssh2_userkey *key, char *passphrase) +bool export_ssh2(const Filename *filename, int type, + struct ssh2_userkey *key, char *passphrase) { if (type == SSH_KEYTYPE_OPENSSH_AUTO) return openssh_auto_write(filename, key, passphrase); @@ -125,7 +123,7 @@ int export_ssh2(const Filename *filename, int type, return openssh_new_write(filename, key, passphrase); if (type == SSH_KEYTYPE_SSHCOM) return sshcom_write(filename, key, passphrase); - return 0; + return false; } /* @@ -168,68 +166,20 @@ void strip_crlf(char *str) /* Primitive versus constructed bit. */ #define ASN1_CONSTRUCTED (1 << 5) -static int ber_read_id_len(void *source, int sourcelen, - int *id, int *length, int *flags) -{ - unsigned char *p = (unsigned char *) source; - - if (sourcelen == 0) - return -1; - - *flags = (*p & 0xE0); - if ((*p & 0x1F) == 0x1F) { - *id = 0; - while (*p & 0x80) { - p++, sourcelen--; - if (sourcelen == 0) - return -1; - *id = (*id << 7) | (*p & 0x7F); - } - p++, sourcelen--; - } else { - *id = *p & 0x1F; - p++, sourcelen--; - } - - if (sourcelen == 0) - return -1; - - if (*p & 0x80) { - unsigned len; - int n = *p & 0x7F; - p++, sourcelen--; - if (sourcelen < n) - return -1; - len = 0; - while (n--) - len = (len << 8) | (*p++); - sourcelen -= n; - *length = toint(len); - } else { - *length = *p; - p++, sourcelen--; - } - - return p - (unsigned char *) source; -} - /* * Write an ASN.1/BER identifier and length pair. Returns the * number of bytes consumed. Assumes dest contains enough space. * Will avoid writing anything if dest is NULL, but still return * amount of space required. */ -static int ber_write_id_len(void *dest, int id, int length, int flags) +static void BinarySink_put_ber_id_len(BinarySink *bs, + int id, int length, int flags) { - unsigned char *d = (unsigned char *)dest; - int len = 0; - if (id <= 30) { /* * Identifier is one byte. */ - len++; - if (d) *d++ = id | flags; + put_byte(bs, id | flags); } else { int n; /* @@ -238,22 +188,18 @@ static int ber_write_id_len(void *dest, int id, int length, int flags) * the identifier, 7 bits at a time, with the top bit of * each byte 1 except the last one which is 0. */ - len++; - if (d) *d++ = 0x1F | flags; + put_byte(bs, 0x1F | flags); for (n = 1; (id >> (7*n)) > 0; n++) continue; /* count the bytes */ - while (n--) { - len++; - if (d) *d++ = (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F); - } + while (n--) + put_byte(bs, (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F)); } if (length < 128) { /* * Length is one byte. */ - len++; - if (d) *d++ = length; + put_byte(bs, length); } else { int n; /* @@ -263,80 +209,57 @@ static int ber_write_id_len(void *dest, int id, int length, int flags) */ for (n = 1; (length >> (8*n)) > 0; n++) continue; /* count the bytes */ - len++; - if (d) *d++ = 0x80 | n; - while (n--) { - len++; - if (d) *d++ = (length >> (8*n)) & 0xFF; - } + put_byte(bs, 0x80 | n); + while (n--) + put_byte(bs, (length >> (8*n)) & 0xFF); } - - return len; } -static int put_uint32(void *target, unsigned val) -{ - unsigned char *d = (unsigned char *)target; +#define put_ber_id_len(bs, id, len, flags) \ + BinarySink_put_ber_id_len(BinarySink_UPCAST(bs), id, len, flags) - PUT_32BIT(d, val); - return 4; -} +typedef struct ber_item { + int id; + int flags; + ptrlen data; +} ber_item; -static int put_string(void *target, const void *data, int len) +static ber_item BinarySource_get_ber(BinarySource *src) { - unsigned char *d = (unsigned char *)target; - - PUT_32BIT(d, len); - memcpy(d+4, data, len); - return len+4; -} - -static int put_string_z(void *target, const char *string) -{ - return put_string(target, string, strlen(string)); -} - -static int put_mp(void *target, void *data, int len) -{ - unsigned char *d = (unsigned char *)target; - unsigned char *i = (unsigned char *)data; - - if (*i & 0x80) { - PUT_32BIT(d, len+1); - d[4] = 0; - memcpy(d+5, data, len); - return len+5; + ber_item toret; + unsigned char leadbyte, lenbyte; + size_t length; + + leadbyte = get_byte(src); + toret.flags = (leadbyte & 0xE0); + if ((leadbyte & 0x1F) == 0x1F) { + unsigned char idbyte; + + toret.id = 0; + do { + idbyte = get_byte(src); + toret.id = (toret.id << 7) | (idbyte & 0x7F); + } while (idbyte & 0x80); } else { - PUT_32BIT(d, len); - memcpy(d+4, data, len); - return len+4; + toret.id = leadbyte & 0x1F; } -} - -/* Simple structure to point to an mp-int within a blob. */ -struct mpint_pos { void *start; int bytes; }; - -static int ssh2_read_mpint(void *data, int len, struct mpint_pos *ret) -{ - int bytes; - unsigned char *d = (unsigned char *) data; - if (len < 4) - goto error; - bytes = toint(GET_32BIT(d)); - if (bytes < 0 || len-4 < bytes) - goto error; - - ret->start = d + 4; - ret->bytes = bytes; - return bytes+4; + lenbyte = get_byte(src); + if (lenbyte & 0x80) { + int nbytes = lenbyte & 0x7F; + length = 0; + while (nbytes-- > 0) + length = (length << 8) | get_byte(src); + } else { + length = lenbyte; + } - error: - ret->start = NULL; - ret->bytes = -1; - return len; /* ensure further calls fail as well */ + toret.data = get_data(src, length); + return toret; } +#define get_ber(bs) BinarySource_get_ber(BinarySource_UPCAST(bs)) + /* ---------------------------------------------------------------------- * Code to read and write OpenSSH private keys, in the old-style PEM * format. @@ -351,13 +274,31 @@ typedef enum { struct openssh_pem_key { openssh_pem_keytype keytype; - int encrypted; + bool encrypted; openssh_pem_enc encryption; char iv[32]; - unsigned char *keyblob; - int keyblob_len, keyblob_size; + strbuf *keyblob; }; +void BinarySink_put_mp_ssh2_from_string( + BinarySink *bs, const void *bytesv, int nbytes) +{ + const unsigned char *bytes = (const unsigned char *)bytesv; + while (nbytes > 0 && bytes[0] == 0) { + nbytes--; + bytes++; + } + if (nbytes > 0 && bytes[0] & 0x80) { + put_uint32(bs, nbytes + 1); + put_byte(bs, 0); + } else { + put_uint32(bs, nbytes); + } + put_data(bs, bytes, nbytes); +} +#define put_mp_ssh2_from_string(bs, val, len) \ + BinarySink_put_mp_ssh2_from_string(BinarySink_UPCAST(bs), val, len) + static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, const char **errmsg_p) { @@ -366,15 +307,14 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, char *line = NULL; const char *errmsg; char *p; - int headers_done; + bool headers_done; char base64_bit[4]; int base64_chars = 0; ret = snew(struct openssh_pem_key); - ret->keyblob = NULL; - ret->keyblob_len = ret->keyblob_size = 0; + ret->keyblob = strbuf_new(); - fp = f_open(filename, "r", FALSE); + fp = f_open(filename, "r", false); if (!fp) { errmsg = "unable to open key file"; goto error; @@ -413,10 +353,10 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, sfree(line); line = NULL; - ret->encrypted = FALSE; + ret->encrypted = false; memset(ret->iv, 0, sizeof(ret->iv)); - headers_done = 0; + headers_done = false; while (1) { if (!(line = fgetline(fp))) { errmsg = "unexpected end of file"; @@ -443,9 +383,9 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, } p += 2; if (!strcmp(p, "ENCRYPTED")) - ret->encrypted = TRUE; + ret->encrypted = true; } else if (!strcmp(line, "DEK-Info")) { - int i, j, ivlen; + int i, ivlen; if (!strncmp(p, "DES-EDE3-CBC,", 13)) { ret->encryption = OP_E_3DES; @@ -459,6 +399,7 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, } p = strchr(p, ',') + 1;/* always non-NULL, by above checks */ for (i = 0; i < ivlen; i++) { + unsigned j; if (1 != sscanf(p, "%2x", &j)) { errmsg = "expected more iv data in DEK-Info"; goto error; @@ -472,7 +413,7 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, } } } else { - headers_done = 1; + headers_done = true; p = line; while (isbase64(*p)) { @@ -490,14 +431,7 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, goto error; } - if (ret->keyblob_len + len > ret->keyblob_size) { - ret->keyblob_size = ret->keyblob_len + len + 256; - ret->keyblob = sresize(ret->keyblob, ret->keyblob_size, - unsigned char); - } - - memcpy(ret->keyblob + ret->keyblob_len, out, len); - ret->keyblob_len += len; + put_data(ret->keyblob, out, len); smemclr(out, sizeof(out)); } @@ -513,12 +447,12 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, fclose(fp); fp = NULL; - if (ret->keyblob_len == 0 || !ret->keyblob) { + if (!ret->keyblob || ret->keyblob->len == 0) { errmsg = "key body not present"; goto error; } - if (ret->encrypted && ret->keyblob_len % 8 != 0) { + if (ret->encrypted && ret->keyblob->len % 8 != 0) { errmsg = "encrypted key blob is not a multiple of " "cipher block size"; goto error; @@ -536,10 +470,8 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, } smemclr(base64_bit, sizeof(base64_bit)); if (ret) { - if (ret->keyblob) { - smemclr(ret->keyblob, ret->keyblob_size); - sfree(ret->keyblob); - } + if (ret->keyblob) + strbuf_free(ret->keyblob); smemclr(ret, sizeof(*ret)); sfree(ret); } @@ -548,39 +480,35 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename, return NULL; } -int openssh_pem_encrypted(const Filename *filename) +static bool openssh_pem_encrypted(const Filename *filename) { struct openssh_pem_key *key = load_openssh_pem_key(filename, NULL); - int ret; + bool ret; if (!key) - return 0; + return false; ret = key->encrypted; - smemclr(key->keyblob, key->keyblob_size); - sfree(key->keyblob); + strbuf_free(key->keyblob); smemclr(key, sizeof(*key)); sfree(key); return ret; } -struct ssh2_userkey *openssh_pem_read(const Filename *filename, - char *passphrase, - const char **errmsg_p) +static struct ssh2_userkey *openssh_pem_read( + const Filename *filename, const char *passphrase, const char **errmsg_p) { struct openssh_pem_key *key = load_openssh_pem_key(filename, errmsg_p); struct ssh2_userkey *retkey; - unsigned char *p, *q; - int ret, id, len, flags; + const ssh_keyalg *alg; + BinarySource src[1]; int i, num_integers; struct ssh2_userkey *retval = NULL; const char *errmsg; - unsigned char *blob; - int blobsize = 0, blobptr, privptr; - char *modptr = NULL; + strbuf *blob = strbuf_new(); + int privptr = 0, publen; + const char *modptr = NULL; int modlen = 0; - blob = NULL; - if (!key) return NULL; @@ -601,29 +529,29 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, unsigned char keybuf[32]; MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); - MD5Update(&md5c, (unsigned char *)key->iv, 8); + put_data(&md5c, passphrase, strlen(passphrase)); + put_data(&md5c, key->iv, 8); MD5Final(keybuf, &md5c); MD5Init(&md5c); - MD5Update(&md5c, keybuf, 16); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); - MD5Update(&md5c, (unsigned char *)key->iv, 8); + put_data(&md5c, keybuf, 16); + put_data(&md5c, passphrase, strlen(passphrase)); + put_data(&md5c, key->iv, 8); MD5Final(keybuf+16, &md5c); /* * Now decrypt the key blob. */ if (key->encryption == OP_E_3DES) - des3_decrypt_pubkey_ossh(keybuf, (unsigned char *)key->iv, - key->keyblob, key->keyblob_len); + des3_decrypt_pubkey_ossh(keybuf, key->iv, + key->keyblob->u, key->keyblob->len); else { - void *ctx; + AESContext *ctx; assert(key->encryption == OP_E_AES); ctx = aes_make_context(); aes128_key(ctx, keybuf); - aes_iv(ctx, (unsigned char *)key->iv); - aes_ssh2_decrypt_blk(ctx, key->keyblob, key->keyblob_len); + aes_iv(ctx, key->iv); + aes_ssh2_decrypt_blk(ctx, key->keyblob->u, key->keyblob->len); aes_free_context(ctx); } @@ -653,17 +581,21 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, * EXPLICIT [0] OID curve, EXPLICIT [1] BIT STRING pubPoint */ - p = key->keyblob; - - /* Expect the SEQUENCE header. Take its absence as a failure to - * decrypt, if the key was encrypted. */ - ret = ber_read_id_len(p, key->keyblob_len, &id, &len, &flags); - p += ret; - if (ret < 0 || id != 16 || len < 0 || - key->keyblob+key->keyblob_len-p < len) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; + BinarySource_BARE_INIT(src, key->keyblob->u, key->keyblob->len); + + { + /* Expect the SEQUENCE header. Take its absence as a failure to + * decrypt, if the key was encrypted. */ + ber_item seq = get_ber(src); + if (get_err(src) || seq.id != 16) { + errmsg = "ASN.1 decoding failure"; + retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; + goto error; + } + + /* Reinitialise our BinarySource to parse just the inside of that + * SEQUENCE. */ + BinarySource_BARE_INIT(src, seq.data.ptr, seq.data.len); } /* Expect a load of INTEGERs. */ @@ -677,126 +609,68 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, if (key->keytype == OP_ECDSA) { /* And now for something completely different */ - unsigned char *priv; - int privlen; - const struct ssh_signkey *alg; + ber_item integer, privkey, sub0, sub1, oid, pubkey; + const ssh_keyalg *alg; const struct ec_curve *curve; - int algnamelen, curvenamelen; - /* Read INTEGER 1 */ - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 2 || len != 1 || - key->keyblob+key->keyblob_len-p < len || p[0] != 1) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - p += 1; - /* Read private key OCTET STRING */ - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 4 || len < 0 || - key->keyblob+key->keyblob_len-p < len) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - priv = p; - privlen = len; - p += len; - /* Read curve OID */ - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 0 || len < 0 || - key->keyblob+key->keyblob_len-p < len) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 6 || len < 0 || - key->keyblob+key->keyblob_len-p < len) { + + /* Parse the outer layer of things inside the containing SEQUENCE */ + integer = get_ber(src); + privkey = get_ber(src); + sub0 = get_ber(src); + sub1 = get_ber(src); + + /* Now look inside sub0 for the curve OID */ + BinarySource_BARE_INIT(src, sub0.data.ptr, sub0.data.len); + oid = get_ber(src); + + /* And inside sub1 for the public-key BIT STRING */ + BinarySource_BARE_INIT(src, sub1.data.ptr, sub1.data.len); + pubkey = get_ber(src); + + if (get_err(src) || + integer.id != 2 || + integer.data.len != 1 || + ((const unsigned char *)integer.data.ptr)[0] != 1 || + privkey.id != 4 || + sub0.id != 0 || + sub1.id != 1 || + oid.id != 6 || + pubkey.id != 3) { + errmsg = "ASN.1 decoding failure"; retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; goto error; } - alg = ec_alg_by_oid(len, p, &curve); + + alg = ec_alg_by_oid(oid.data.len, oid.data.ptr, &curve); if (!alg) { errmsg = "Unsupported ECDSA curve."; retval = NULL; goto error; } - p += len; - /* Read BIT STRING point */ - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 1 || len < 0 || - key->keyblob+key->keyblob_len-p < len) { + if (pubkey.data.len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) { errmsg = "ASN.1 decoding failure"; retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; goto error; } - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 3 || len < 0 || - key->keyblob+key->keyblob_len-p < len || - len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - p += 1; len -= 1; /* Skip 0x00 before point */ + /* Skip 0x00 before point */ + pubkey.data.ptr = (const char *)pubkey.data.ptr + 1; + pubkey.data.len -= 1; /* Construct the key */ retkey = snew(struct ssh2_userkey); - if (!retkey) { - errmsg = "out of memory"; - goto error; - } - retkey->alg = alg; - blob = snewn((4+19 + 4+8 + 4+len) + (4+1+privlen), unsigned char); - if (!blob) { - sfree(retkey); - errmsg = "out of memory"; - goto error; - } - - q = blob; - - algnamelen = strlen(alg->name); - PUT_32BIT(q, algnamelen); q += 4; - memcpy(q, alg->name, algnamelen); q += algnamelen; - curvenamelen = strlen(curve->name); - PUT_32BIT(q, curvenamelen); q += 4; - memcpy(q, curve->name, curvenamelen); q += curvenamelen; + put_stringz(blob, alg->ssh_id); + put_stringz(blob, curve->name); + put_stringpl(blob, pubkey.data); + publen = blob->len; + put_mp_ssh2_from_string(blob, privkey.data.ptr, privkey.data.len); - PUT_32BIT(q, len); q += 4; - memcpy(q, p, len); q += len; + retkey->key = ssh_key_new_priv( + alg, make_ptrlen(blob->u, publen), + make_ptrlen(blob->u + publen, blob->len - publen)); - /* - * To be acceptable to our createkey(), the private blob must - * contain a valid mpint, i.e. without the top bit set. But - * the input private string may have the top bit set, so we - * prefix a zero byte to ensure createkey() doesn't fail for - * that reason. - */ - PUT_32BIT(q, privlen+1); - q[4] = 0; - memcpy(q+5, priv, privlen); - - retkey->data = retkey->alg->createkey(retkey->alg, - blob, q-blob, - q, 5+privlen); - - if (!retkey->data) { + if (!retkey->key) { sfree(retkey); errmsg = "unable to create key data structure"; goto error; @@ -804,25 +678,12 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, } else if (key->keytype == OP_RSA || key->keytype == OP_DSA) { - /* - * Space to create key blob in. - */ - blobsize = 256+key->keyblob_len; - blob = snewn(blobsize, unsigned char); - PUT_32BIT(blob, 7); - if (key->keytype == OP_DSA) - memcpy(blob+4, "ssh-dss", 7); - else if (key->keytype == OP_RSA) - memcpy(blob+4, "ssh-rsa", 7); - blobptr = 4+7; - privptr = -1; + put_stringz(blob, key->keytype == OP_DSA ? "ssh-dss" : "ssh-rsa"); for (i = 0; i < num_integers; i++) { - ret = ber_read_id_len(p, key->keyblob+key->keyblob_len-p, - &id, &len, &flags); - p += ret; - if (ret < 0 || id != 2 || len < 0 || - key->keyblob+key->keyblob_len-p < len) { + ber_item integer = get_ber(src); + + if (get_err(src) || integer.id != 2) { errmsg = "ASN.1 decoding failure"; retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; goto error; @@ -833,7 +694,8 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, * The first integer should be zero always (I think * this is some sort of version indication). */ - if (len != 1 || p[0] != 0) { + if (integer.data.len != 1 || + ((const unsigned char *)integer.data.ptr)[0] != 0) { errmsg = "version number mismatch"; goto error; } @@ -845,17 +707,14 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, */ if (i == 1) { /* Save the details for after we deal with number 2. */ - modptr = (char *)p; - modlen = len; + modptr = integer.data.ptr; + modlen = integer.data.len; } else if (i != 6 && i != 7) { - PUT_32BIT(blob+blobptr, len); - memcpy(blob+blobptr+4, p, len); - blobptr += 4+len; + put_mp_ssh2_from_string(blob, integer.data.ptr, + integer.data.len); if (i == 2) { - PUT_32BIT(blob+blobptr, modlen); - memcpy(blob+blobptr+4, modptr, modlen); - blobptr += 4+modlen; - privptr = blobptr; + put_mp_ssh2_from_string(blob, modptr, modlen); + privptr = blob->len; } } } else if (key->keytype == OP_DSA) { @@ -863,15 +722,11 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, * Integers 1-4 go into the public blob; integer 5 goes * into the private blob. */ - PUT_32BIT(blob+blobptr, len); - memcpy(blob+blobptr+4, p, len); - blobptr += 4+len; + put_mp_ssh2_from_string(blob, integer.data.ptr, + integer.data.len); if (i == 4) - privptr = blobptr; + privptr = blob->len; } - - /* Skip past the number. */ - p += len; } /* @@ -882,11 +737,12 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, */ assert(privptr > 0); /* should have bombed by now if not */ retkey = snew(struct ssh2_userkey); - retkey->alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss); - retkey->data = retkey->alg->createkey(retkey->alg, blob, privptr, - blob+privptr, - blobptr-privptr); - if (!retkey->data) { + alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss); + retkey->key = ssh_key_new_priv( + alg, make_ptrlen(blob->u, privptr), + make_ptrlen(blob->u+privptr, blob->len-privptr)); + + if (!retkey->key) { sfree(retkey); errmsg = "unable to create key data structure"; goto error; @@ -908,48 +764,48 @@ struct ssh2_userkey *openssh_pem_read(const Filename *filename, retval = retkey; error: - if (blob) { - smemclr(blob, blobsize); - sfree(blob); - } - smemclr(key->keyblob, key->keyblob_size); - sfree(key->keyblob); + strbuf_free(blob); + strbuf_free(key->keyblob); smemclr(key, sizeof(*key)); sfree(key); if (errmsg_p) *errmsg_p = errmsg; return retval; } -int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase) +static bool openssh_pem_write( + const Filename *filename, struct ssh2_userkey *key, const char *passphrase) { - unsigned char *pubblob, *privblob, *spareblob; - int publen, privlen, sparelen = 0; - unsigned char *outblob; - int outlen; - struct mpint_pos numbers[9]; - int nnumbers, pos, len, seqlen, i; + strbuf *pubblob, *privblob, *outblob; + unsigned char *spareblob; + int sparelen = 0; + ptrlen numbers[9]; + int nnumbers, i; const char *header, *footer; char zero[1]; unsigned char iv[8]; - int ret = 0; + bool ret = false; FILE *fp; + BinarySource src[1]; /* * Fetch the key blobs. */ - pubblob = key->alg->public_blob(key->data, &publen); - privblob = key->alg->private_blob(key->data, &privlen); - spareblob = outblob = NULL; + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new(); + ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); + spareblob = NULL; - outblob = NULL; - len = 0; + outblob = strbuf_new(); /* * Encode the OpenSSH key blob, and also decide on the header * line. */ - if (key->alg == &ssh_rsa || key->alg == &ssh_dss) { + if (ssh_key_alg(key->key) == &ssh_rsa || + ssh_key_alg(key->key) == &ssh_dss) { + strbuf *seq; + /* * The RSA and DSS handlers share some code because the two * key types have very similar ASN.1 representations, as a @@ -957,30 +813,30 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, * bignums per key type and then construct the actual blob in * common code after that. */ - if (key->alg == &ssh_rsa) { - int pos; - struct mpint_pos n, e, d, p, q, iqmp, dmp1, dmq1; + if (ssh_key_alg(key->key) == &ssh_rsa) { + ptrlen n, e, d, p, q, iqmp, dmp1, dmq1; Bignum bd, bp, bq, bdmp1, bdmq1; /* * These blobs were generated from inside PuTTY, so we needn't * treat them as untrusted. */ - pos = 4 + GET_32BIT(pubblob); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); - pos = 0; - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); - - assert(e.start && iqmp.start); /* can't go wrong */ + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + e = get_string(src); + n = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + d = get_string(src); + p = get_string(src); + q = get_string(src); + iqmp = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ /* We also need d mod (p-1) and d mod (q-1). */ - bd = bignum_from_bytes(d.start, d.bytes); - bp = bignum_from_bytes(p.start, p.bytes); - bq = bignum_from_bytes(q.start, q.bytes); + bd = bignum_from_bytes(d.ptr, d.len); + bp = bignum_from_bytes(p.ptr, p.len); + bq = bignum_from_bytes(q.ptr, q.len); decbn(bp); decbn(bq); bdmp1 = bigmod(bd, bp); @@ -989,20 +845,20 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, freebn(bp); freebn(bq); - dmp1.bytes = (bignum_bitcount(bdmp1)+8)/8; - dmq1.bytes = (bignum_bitcount(bdmq1)+8)/8; - sparelen = dmp1.bytes + dmq1.bytes; + dmp1.len = (bignum_bitcount(bdmp1)+8)/8; + dmq1.len = (bignum_bitcount(bdmq1)+8)/8; + sparelen = dmp1.len + dmq1.len; spareblob = snewn(sparelen, unsigned char); - dmp1.start = spareblob; - dmq1.start = spareblob + dmp1.bytes; - for (i = 0; i < dmp1.bytes; i++) - spareblob[i] = bignum_byte(bdmp1, dmp1.bytes-1 - i); - for (i = 0; i < dmq1.bytes; i++) - spareblob[i+dmp1.bytes] = bignum_byte(bdmq1, dmq1.bytes-1 - i); + dmp1.ptr = spareblob; + dmq1.ptr = spareblob + dmp1.len; + for (i = 0; i < dmp1.len; i++) + spareblob[i] = bignum_byte(bdmp1, dmp1.len-1 - i); + for (i = 0; i < dmq1.len; i++) + spareblob[i+dmp1.len] = bignum_byte(bdmq1, dmq1.len-1 - i); freebn(bdmp1); freebn(bdmq1); - numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + numbers[0] = make_ptrlen(zero, 1); zero[0] = '\0'; numbers[1] = n; numbers[2] = e; numbers[3] = d; @@ -1016,24 +872,24 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, header = "-----BEGIN RSA PRIVATE KEY-----\n"; footer = "-----END RSA PRIVATE KEY-----\n"; } else { /* ssh-dss */ - int pos; - struct mpint_pos p, q, g, y, x; + ptrlen p, q, g, y, x; /* * These blobs were generated from inside PuTTY, so we needn't * treat them as untrusted. */ - pos = 4 + GET_32BIT(pubblob); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); - pos = 0; - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); - - assert(y.start && x.start); /* can't go wrong */ - - numbers[0].start = zero; numbers[0].bytes = 1; zero[0] = '\0'; + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + p = get_string(src); + q = get_string(src); + g = get_string(src); + y = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + x = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ + + numbers[0].ptr = zero; numbers[0].len = 1; zero[0] = '\0'; numbers[1] = p; numbers[2] = q; numbers[3] = g; @@ -1045,40 +901,22 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, footer = "-----END DSA PRIVATE KEY-----\n"; } - /* - * Now count up the total size of the ASN.1 encoded integers, - * so as to determine the length of the containing SEQUENCE. - */ - len = 0; - for (i = 0; i < nnumbers; i++) { - len += ber_write_id_len(NULL, 2, numbers[i].bytes, 0); - len += numbers[i].bytes; - } - seqlen = len; - /* Now add on the SEQUENCE header. */ - len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED); - - /* - * Now we know how big outblob needs to be. Allocate it. - */ - outblob = snewn(len, unsigned char); - - /* - * And write the data into it. - */ - pos = 0; - pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED); + seq = strbuf_new(); for (i = 0; i < nnumbers; i++) { - pos += ber_write_id_len(outblob+pos, 2, numbers[i].bytes, 0); - memcpy(outblob+pos, numbers[i].start, numbers[i].bytes); - pos += numbers[i].bytes; + put_ber_id_len(seq, 2, numbers[i].len, 0); + put_data(seq, numbers[i].ptr, numbers[i].len); } - } else if (key->alg == &ssh_ecdsa_nistp256 || - key->alg == &ssh_ecdsa_nistp384 || - key->alg == &ssh_ecdsa_nistp521) { + 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) { const unsigned char *oid; + struct ec_key *ec = container_of(key->key, struct ec_key, sshk); int oidlen; int pointlen; + strbuf *seq, *sub; /* * Structure of asn1: @@ -1090,51 +928,46 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, * [1] * BIT STRING (0x00 public key point) */ - oid = ec_alg_oid(key->alg, &oidlen); - pointlen = (((struct ec_key *)key->data)->publicKey.curve->fieldBits - + 7) / 8 * 2; - - len = ber_write_id_len(NULL, 2, 1, 0); - len += 1; - len += ber_write_id_len(NULL, 4, privlen - 4, 0); - len+= privlen - 4; - len += ber_write_id_len(NULL, 0, oidlen + - ber_write_id_len(NULL, 6, oidlen, 0), - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - len += ber_write_id_len(NULL, 6, oidlen, 0); - len += oidlen; - len += ber_write_id_len(NULL, 1, 2 + pointlen + - ber_write_id_len(NULL, 3, 2 + pointlen, 0), - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - len += ber_write_id_len(NULL, 3, 2 + pointlen, 0); - len += 2 + pointlen; - - seqlen = len; - len += ber_write_id_len(NULL, 16, seqlen, ASN1_CONSTRUCTED); - - outblob = snewn(len, unsigned char); - assert(outblob); - - pos = 0; - pos += ber_write_id_len(outblob+pos, 16, seqlen, ASN1_CONSTRUCTED); - pos += ber_write_id_len(outblob+pos, 2, 1, 0); - outblob[pos++] = 1; - pos += ber_write_id_len(outblob+pos, 4, privlen - 4, 0); - memcpy(outblob+pos, privblob + 4, privlen - 4); - pos += privlen - 4; - pos += ber_write_id_len(outblob+pos, 0, oidlen + - ber_write_id_len(NULL, 6, oidlen, 0), - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - pos += ber_write_id_len(outblob+pos, 6, oidlen, 0); - memcpy(outblob+pos, oid, oidlen); - pos += oidlen; - pos += ber_write_id_len(outblob+pos, 1, 2 + pointlen + - ber_write_id_len(NULL, 3, 2 + pointlen, 0), - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - pos += ber_write_id_len(outblob+pos, 3, 2 + pointlen, 0); - outblob[pos++] = 0; - memcpy(outblob+pos, pubblob+39, 1 + pointlen); - pos += 1 + pointlen; + oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen); + pointlen = (ec->publicKey.curve->fieldBits + 7) / 8 * 2; + + seq = strbuf_new(); + + /* INTEGER 1 */ + put_ber_id_len(seq, 2, 1, 0); + put_byte(seq, 1); + + /* OCTET STRING private key */ + put_ber_id_len(seq, 4, privblob->len - 4, 0); + put_data(seq, privblob->s + 4, privblob->len - 4); + + /* Subsidiary OID */ + sub = strbuf_new(); + put_ber_id_len(sub, 6, oidlen, 0); + put_data(sub, oid, oidlen); + + /* Append the OID to the sequence */ + put_ber_id_len(seq, 0, sub->len, + ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); + put_data(seq, sub->s, sub->len); + strbuf_free(sub); + + /* Subsidiary BIT STRING */ + sub = strbuf_new(); + put_ber_id_len(sub, 3, 2 + pointlen, 0); + put_byte(sub, 0); + put_data(sub, pubblob->s+39, 1 + pointlen); + + /* Append the BIT STRING to the sequence */ + put_ber_id_len(seq, 1, sub->len, + ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); + put_data(seq, sub->s, sub->len); + strbuf_free(sub); + + /* Write the full sequence with header to the output blob. */ + put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); + put_data(outblob, seq->s, seq->len); + strbuf_free(seq); header = "-----BEGIN EC PRIVATE KEY-----\n"; footer = "-----END EC PRIVATE KEY-----\n"; @@ -1152,19 +985,7 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, if (passphrase) { struct MD5Context md5c; unsigned char keybuf[32]; - - /* - * Round up to the cipher block size, ensuring we have at - * least one byte of padding (see below). - */ - outlen = (len+8) &~ 7; - { - unsigned char *tmp = snewn(outlen, unsigned char); - memcpy(tmp, outblob, len); - smemclr(outblob, len); - sfree(outblob); - outblob = tmp; - } + int origlen, outlen, pad, i; /* * Padding on OpenSSH keys is deterministic. The number of @@ -1183,10 +1004,10 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, * with the same value. Those are all removed and the rest is * returned. */ - assert(pos == len); - while (pos < outlen) { - outblob[pos++] = outlen - len; - } + origlen = outblob->len; + outlen = (origlen + 8) &~ 7; + pad = outlen - origlen; + put_padding(outblob, pad, pad); /* * Invent an iv. Then derive encryption key from passphrase @@ -1200,36 +1021,31 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, for (i = 0; i < 8; i++) iv[i] = random_byte(); MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); - MD5Update(&md5c, iv, 8); + put_data(&md5c, passphrase, strlen(passphrase)); + put_data(&md5c, iv, 8); MD5Final(keybuf, &md5c); MD5Init(&md5c); - MD5Update(&md5c, keybuf, 16); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); - MD5Update(&md5c, iv, 8); + put_data(&md5c, keybuf, 16); + put_data(&md5c, passphrase, strlen(passphrase)); + put_data(&md5c, iv, 8); MD5Final(keybuf+16, &md5c); /* * Now encrypt the key blob. */ - des3_encrypt_pubkey_ossh(keybuf, iv, outblob, outlen); + des3_encrypt_pubkey_ossh(keybuf, iv, + outblob->u, outlen); smemclr(&md5c, sizeof(md5c)); smemclr(keybuf, sizeof(keybuf)); - } else { - /* - * If no encryption, the blob has exactly its original - * cleartext size. - */ - outlen = len; } /* * And save it. We'll use Unix line endings just in case it's * subsequently transferred in binary mode. */ - fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ + fp = f_open(filename, "wb", true); /* ensure Unix line endings */ if (!fp) goto error; fputs(header, fp); @@ -1239,28 +1055,22 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, fprintf(fp, "%02X", iv[i]); fprintf(fp, "\n\n"); } - base64_encode(fp, outblob, outlen, 64); + base64_encode(fp, outblob->u, outblob->len, 64); fputs(footer, fp); fclose(fp); - ret = 1; + ret = true; error: - if (outblob) { - smemclr(outblob, outlen); - sfree(outblob); - } + if (outblob) + strbuf_free(outblob); if (spareblob) { smemclr(spareblob, sparelen); sfree(spareblob); } - if (privblob) { - smemclr(privblob, privlen); - sfree(privblob); - } - if (pubblob) { - smemclr(pubblob, publen); - sfree(pubblob); - } + if (privblob) + strbuf_free(privblob); + if (pubblob) + strbuf_free(pubblob); return ret; } @@ -1269,7 +1079,7 @@ int openssh_pem_write(const Filename *filename, struct ssh2_userkey *key, */ typedef enum { - ON_E_NONE, ON_E_AES256CBC + ON_E_NONE, ON_E_AES256CBC, ON_E_AES256CTR } openssh_new_cipher; typedef enum { ON_K_NONE, ON_K_BCRYPT @@ -1283,14 +1093,12 @@ struct openssh_new_key { int rounds; /* This points to a position within keyblob, not a * separately allocated thing */ - const unsigned char *salt; - int saltlen; + ptrlen salt; } bcrypt; } kdfopts; int nkeys, key_wanted; /* This too points to a position within keyblob */ - unsigned char *privatestr; - int privatelen; + ptrlen private; unsigned char *keyblob; int keyblob_len, keyblob_size; @@ -1306,17 +1114,15 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename, char *p; char base64_bit[4]; int base64_chars = 0; - const void *filedata; - int filelen; - const void *string, *kdfopts, *bcryptsalt, *pubkey; - int stringlen, kdfoptlen, bcryptsaltlen, pubkeylen; - unsigned bcryptrounds, nkeys, key_index; + BinarySource src[1]; + ptrlen str; + unsigned key_index; ret = snew(struct openssh_new_key); ret->keyblob = NULL; ret->keyblob_len = ret->keyblob_size = 0; - fp = f_open(filename, "r", FALSE); + fp = f_open(filename, "r", false); if (!fp) { errmsg = "unable to open key file"; goto error; @@ -1390,66 +1196,61 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename, goto error; } - filedata = ret->keyblob; - filelen = ret->keyblob_len; + BinarySource_BARE_INIT(src, ret->keyblob, ret->keyblob_len); - if (filelen < 15 || 0 != memcmp(filedata, "openssh-key-v1\0", 15)) { + if (strcmp(get_asciz(src), "openssh-key-v1") != 0) { errmsg = "new-style OpenSSH magic number missing\n"; goto error; } - filedata = (const char *)filedata + 15; - filelen -= 15; - if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) { - errmsg = "encountered EOF before cipher name\n"; - goto error; - } - if (match_ssh_id(stringlen, string, "none")) { + /* Cipher name */ + str = get_string(src); + if (ptrlen_eq_string(str, "none")) { ret->cipher = ON_E_NONE; - } else if (match_ssh_id(stringlen, string, "aes256-cbc")) { + } else if (ptrlen_eq_string(str, "aes256-cbc")) { ret->cipher = ON_E_AES256CBC; + } else if (ptrlen_eq_string(str, "aes256-ctr")) { + ret->cipher = ON_E_AES256CTR; } else { - errmsg = "unrecognised cipher name\n"; + errmsg = get_err(src) ? "no cipher name found" : + "unrecognised cipher name\n"; goto error; } - if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) { - errmsg = "encountered EOF before kdf name\n"; - goto error; - } - if (match_ssh_id(stringlen, string, "none")) { + /* Key derivation function name */ + str = get_string(src); + if (ptrlen_eq_string(str, "none")) { ret->kdf = ON_K_NONE; - } else if (match_ssh_id(stringlen, string, "bcrypt")) { + } else if (ptrlen_eq_string(str, "bcrypt")) { ret->kdf = ON_K_BCRYPT; } else { - errmsg = "unrecognised kdf name\n"; + errmsg = get_err(src) ? "no kdf name found" : + "unrecognised kdf name\n"; goto error; } - if (!(kdfopts = get_ssh_string(&filelen, &filedata, &kdfoptlen))) { - errmsg = "encountered EOF before kdf options\n"; - goto error; - } + /* KDF extra options */ + str = get_string(src); switch (ret->kdf) { case ON_K_NONE: - if (kdfoptlen != 0) { + if (str.len != 0) { errmsg = "expected empty options string for 'none' kdf"; goto error; } break; case ON_K_BCRYPT: - if (!(bcryptsalt = get_ssh_string(&kdfoptlen, &kdfopts, - &bcryptsaltlen))) { - errmsg = "bcrypt options string did not contain salt\n"; - goto error; - } - if (!get_ssh_uint32(&kdfoptlen, &kdfopts, &bcryptrounds)) { - errmsg = "bcrypt options string did not contain round count\n"; - goto error; + { + BinarySource opts[1]; + + BinarySource_BARE_INIT(opts, str.ptr, str.len); + ret->kdfopts.bcrypt.salt = get_string(opts); + ret->kdfopts.bcrypt.rounds = get_uint32(opts); + + if (get_err(opts)) { + errmsg = "failed to parse bcrypt options string"; + goto error; + } } - ret->kdfopts.bcrypt.salt = bcryptsalt; - ret->kdfopts.bcrypt.saltlen = bcryptsaltlen; - ret->kdfopts.bcrypt.rounds = bcryptrounds; break; } @@ -1465,35 +1266,27 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename, * 'key_wanted' field is set to a value in the range [0, * nkeys) by some mechanism. */ - if (!get_ssh_uint32(&filelen, &filedata, &nkeys)) { - errmsg = "encountered EOF before key count\n"; + ret->nkeys = toint(get_uint32(src)); + if (ret->nkeys != 1) { + errmsg = get_err(src) ? "no key count found" : + "multiple keys in new-style OpenSSH key file not supported\n"; goto error; } - if (nkeys != 1) { - errmsg = "multiple keys in new-style OpenSSH key file " - "not supported\n"; - goto error; - } - ret->nkeys = nkeys; ret->key_wanted = 0; - for (key_index = 0; key_index < nkeys; key_index++) { - if (!(pubkey = get_ssh_string(&filelen, &filedata, &pubkeylen))) { - errmsg = "encountered EOF before kdf options\n"; - goto error; - } - } + /* Read and ignore a string per public key. */ + for (key_index = 0; key_index < ret->nkeys; key_index++) + str = get_string(src); /* * Now we expect a string containing the encrypted part of the * key file. */ - if (!(string = get_ssh_string(&filelen, &filedata, &stringlen))) { - errmsg = "encountered EOF before private key container\n"; + ret->private = get_string(src); + if (get_err(src)) { + errmsg = "no private key container string found\n"; goto error; } - ret->privatestr = (unsigned char *)string; - ret->privatelen = stringlen; /* * And now we're done, until asked to actually decrypt. @@ -1523,13 +1316,13 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename, return NULL; } -int openssh_new_encrypted(const Filename *filename) +static bool openssh_new_encrypted(const Filename *filename) { struct openssh_new_key *key = load_openssh_new_key(filename, NULL); - int ret; + bool ret; if (!key) - return 0; + return false; ret = (key->cipher != ON_E_NONE); smemclr(key->keyblob, key->keyblob_size); sfree(key->keyblob); @@ -1538,19 +1331,17 @@ int openssh_new_encrypted(const Filename *filename) return ret; } -struct ssh2_userkey *openssh_new_read(const Filename *filename, - char *passphrase, - const char **errmsg_p) +static struct ssh2_userkey *openssh_new_read( + const Filename *filename, const char *passphrase, const char **errmsg_p) { struct openssh_new_key *key = load_openssh_new_key(filename, errmsg_p); struct ssh2_userkey *retkey = NULL; - int i; struct ssh2_userkey *retval = NULL; const char *errmsg; - unsigned checkint0, checkint1; - const void *priv, *string; - int privlen, stringlen, key_index; - const struct ssh_signkey *alg = NULL; + unsigned checkint; + BinarySource src[1]; + int key_index; + const ssh_keyalg *alg = NULL; if (!key) return NULL; @@ -1567,6 +1358,7 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, keysize = 0; break; case ON_E_AES256CBC: + case ON_E_AES256CTR: keysize = 48; /* 32 byte key + 16 byte IV */ break; default: @@ -1579,8 +1371,8 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, break; case ON_K_BCRYPT: openssh_bcrypt(passphrase, - key->kdfopts.bcrypt.salt, - key->kdfopts.bcrypt.saltlen, + key->kdfopts.bcrypt.salt.ptr, + key->kdfopts.bcrypt.salt.len, key->kdfopts.bcrypt.rounds, keybuf, keysize); break; @@ -1591,7 +1383,8 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, case ON_E_NONE: break; case ON_E_AES256CBC: - if (key->privatelen % 16 != 0) { + case ON_E_AES256CTR: + if (key->private.len % 16 != 0) { errmsg = "private key container length is not a" " multiple of AES block size\n"; goto error; @@ -1600,8 +1393,16 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, void *ctx = aes_make_context(); aes256_key(ctx, keybuf); aes_iv(ctx, keybuf + 32); - aes_ssh2_decrypt_blk(ctx, key->privatestr, - key->privatelen); + /* Decrypt the private section in place, casting away + * the const from key->private being a ptrlen */ + if (key->cipher == ON_E_AES256CBC) { + aes_ssh2_decrypt_blk(ctx, (char *)key->private.ptr, + key->private.len); + } + else { + aes_ssh2_sdctr(ctx, (char *)key->private.ptr, + key->private.len); + } aes_free_context(ctx); } break; @@ -1614,86 +1415,63 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, * Now parse the entire encrypted section, and extract the key * identified by key_wanted. */ - priv = key->privatestr; - privlen = key->privatelen; + BinarySource_BARE_INIT(src, key->private.ptr, key->private.len); - if (!get_ssh_uint32(&privlen, &priv, &checkint0) || - !get_ssh_uint32(&privlen, &priv, &checkint1) || - checkint0 != checkint1) { + checkint = get_uint32(src); + if (get_uint32(src) != checkint || get_err(src)) { errmsg = "decryption check failed"; goto error; } - retkey = NULL; - for (key_index = 0; key_index < key->nkeys; key_index++) { - const unsigned char *thiskey; - int thiskeylen; + retkey = snew(struct ssh2_userkey); + retkey->key = NULL; + retkey->comment = NULL; - /* - * Read the key type, which will tell us how to scan over - * the key to get to the next one. - */ - if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) { - errmsg = "expected key type in private string"; - goto error; - } + for (key_index = 0; key_index < key->nkeys; key_index++) { + ptrlen comment; /* - * Preliminary key type identification, and decide how - * many pieces of key we expect to see. Currently - * (conveniently) all key types can be seen as some number - * of strings, so we just need to know how many of them to - * skip over. (The numbers below exclude the key comment.) + * Identify the key type. */ - { - /* find_pubkey_alg needs a zero-terminated copy of the - * algorithm name */ - char *name_zt = dupprintf("%.*s", stringlen, (char *)string); - alg = find_pubkey_alg(name_zt); - sfree(name_zt); - } - + alg = find_pubkey_alg_len(get_string(src)); if (!alg) { errmsg = "private key type not recognised\n"; goto error; } - thiskey = priv; - /* - * Skip over the pieces of key. + * Read the key. We have to do this even if it's not the one + * we want, because it's the only way to find out how much + * data to skip past to get to the next key in the file. */ - for (i = 0; i < alg->openssh_private_npieces; i++) { - if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) { - errmsg = "ran out of data in mid-private-key"; - goto error; - } + retkey->key = ssh_key_new_priv_openssh(alg, src); + if (get_err(src)) { + errmsg = "unable to read entire private key"; + goto error; } - - thiskeylen = (int)((const unsigned char *)priv - - (const unsigned char *)thiskey); - if (key_index == key->key_wanted) { - retkey = snew(struct ssh2_userkey); - retkey->comment = NULL; - retkey->alg = alg; - retkey->data = alg->openssh_createkey(alg, &thiskey, &thiskeylen); - if (!retkey->data) { - errmsg = "unable to create key data structure"; - goto error; - } + if (!retkey->key) { + errmsg = "unable to create key data structure"; + goto error; + } + if (key_index != key->key_wanted) { + /* + * If this isn't the key we're looking for, throw it away. + */ + ssh_key_free(retkey->key); + retkey->key = NULL; } /* * Read the key comment. */ - if (!(string = get_ssh_string(&privlen, &priv, &stringlen))) { - errmsg = "ran out of data at key comment"; + comment = get_string(src); + if (get_err(src)) { + errmsg = "unable to read key comment"; goto error; } if (key_index == key->key_wanted) { assert(retkey); - retkey->comment = dupprintf("%.*s", stringlen, - (const char *)string); + retkey->comment = mkstr(comment); } } @@ -1705,11 +1483,13 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, /* * Now we expect nothing left but padding. */ - for (i = 0; i < privlen; i++) { - if (((const unsigned char *)priv)[i] != (unsigned char)(i+1)) { - errmsg = "padding at end of private string did not match"; - goto error; - } + { + unsigned char expected_pad_byte = 1; + while (get_avail(src) > 0) + if (get_byte(src) != expected_pad_byte++) { + errmsg = "padding at end of private string did not match"; + goto error; + } } errmsg = NULL; /* no error */ @@ -1719,10 +1499,8 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, error: if (retkey) { sfree(retkey->comment); - if (retkey->data) { - assert(alg); - alg->freekey(retkey->data); - } + if (retkey->key) + ssh_key_free(retkey->key); sfree(retkey); } smemclr(key->keyblob, key->keyblob_size); @@ -1733,14 +1511,13 @@ struct ssh2_userkey *openssh_new_read(const Filename *filename, return retval; } -int openssh_new_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase) +static bool openssh_new_write( + const Filename *filename, struct ssh2_userkey *key, const char *passphrase) { - unsigned char *pubblob, *privblob, *outblob, *p; - unsigned char *private_section_start, *private_section_length_field; - int publen, privlen, commentlen, maxsize, padvalue, i; + strbuf *pubblob, *privblob, *cblob; + int padvalue, i; unsigned checkint; - int ret = 0; + bool ret = false; unsigned char bcrypt_salt[16]; const int bcrypt_rounds = 16; FILE *fp; @@ -1748,146 +1525,114 @@ int openssh_new_write(const Filename *filename, struct ssh2_userkey *key, /* * Fetch the key blobs and find out the lengths of things. */ - pubblob = key->alg->public_blob(key->data, &publen); - i = key->alg->openssh_fmtkey(key->data, NULL, 0); - privblob = snewn(i, unsigned char); - privlen = key->alg->openssh_fmtkey(key->data, privblob, i); - assert(privlen == i); - commentlen = strlen(key->comment); - - /* - * Allocate enough space for the full binary key format. No need - * to be absolutely precise here. - */ - maxsize = (16 + /* magic number */ - 32 + /* cipher name string */ - 32 + /* kdf name string */ - 64 + /* kdf options string */ - 4 + /* key count */ - 4+publen + /* public key string */ - 4 + /* string header for private section */ - 8 + /* checkint x 2 */ - 4+strlen(key->alg->name) + /* key type string */ - privlen + /* private blob */ - 4+commentlen + /* comment string */ - 16); /* padding at end of private section */ - outblob = snewn(maxsize, unsigned char); + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new(); + ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob)); /* * Construct the cleartext version of the blob. */ - p = outblob; + cblob = strbuf_new(); /* Magic number. */ - memcpy(p, "openssh-key-v1\0", 15); - p += 15; + put_asciz(cblob, "openssh-key-v1"); /* Cipher and kdf names, and kdf options. */ if (!passphrase) { memset(bcrypt_salt, 0, sizeof(bcrypt_salt)); /* prevent warnings */ - p += put_string_z(p, "none"); - p += put_string_z(p, "none"); - p += put_string_z(p, ""); + put_stringz(cblob, "none"); + put_stringz(cblob, "none"); + put_stringz(cblob, ""); } else { - unsigned char *q; + strbuf *substr; + for (i = 0; i < (int)sizeof(bcrypt_salt); i++) bcrypt_salt[i] = random_byte(); - p += put_string_z(p, "aes256-cbc"); - p += put_string_z(p, "bcrypt"); - q = p; - p += 4; - p += put_string(p, bcrypt_salt, sizeof(bcrypt_salt)); - p += put_uint32(p, bcrypt_rounds); - PUT_32BIT_MSB_FIRST(q, (unsigned)(p - (q+4))); + put_stringz(cblob, "aes256-ctr"); + put_stringz(cblob, "bcrypt"); + substr = strbuf_new(); + put_string(substr, bcrypt_salt, sizeof(bcrypt_salt)); + put_uint32(substr, bcrypt_rounds); + put_stringsb(cblob, substr); } /* Number of keys. */ - p += put_uint32(p, 1); + put_uint32(cblob, 1); /* Public blob. */ - p += put_string(p, pubblob, publen); - - /* Begin private section. */ - private_section_length_field = p; - p += 4; - private_section_start = p; - - /* checkint. */ - checkint = 0; - for (i = 0; i < 4; i++) - checkint = (checkint << 8) + random_byte(); - p += put_uint32(p, checkint); - p += put_uint32(p, checkint); - - /* Private key. The main private blob goes inline, with no string - * wrapper. */ - p += put_string_z(p, key->alg->name); - memcpy(p, privblob, privlen); - p += privlen; - - /* Comment. */ - p += put_string_z(p, key->comment); - - /* Pad out the encrypted section. */ - padvalue = 1; - do { - *p++ = padvalue++; - } while ((p - private_section_start) & 15); + put_string(cblob, pubblob->s, pubblob->len); - assert(p - outblob < maxsize); - - /* Go back and fill in the length field for the private section. */ - PUT_32BIT_MSB_FIRST(private_section_length_field, - p - private_section_start); + /* Private section. */ + { + strbuf *cpblob = strbuf_new(); + + /* checkint. */ + checkint = 0; + for (i = 0; i < 4; i++) + checkint = (checkint << 8) + random_byte(); + put_uint32(cpblob, checkint); + put_uint32(cpblob, checkint); + + /* Private key. The main private blob goes inline, with no string + * wrapper. */ + put_stringz(cpblob, ssh_key_ssh_id(key->key)); + put_data(cpblob, privblob->s, privblob->len); + + /* Comment. */ + put_stringz(cpblob, key->comment); + + /* Pad out the encrypted section. */ + padvalue = 1; + do { + put_byte(cpblob, padvalue++); + } while (cpblob->len & 15); + + if (passphrase) { + /* + * Encrypt the private section. We need 48 bytes of key + * material: 32 bytes AES key + 16 bytes iv. + */ + unsigned char keybuf[48]; + void *ctx; - if (passphrase) { - /* - * Encrypt the private section. We need 48 bytes of key - * material: 32 bytes AES key + 16 bytes iv. - */ - unsigned char keybuf[48]; - void *ctx; + openssh_bcrypt(passphrase, + bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds, + keybuf, sizeof(keybuf)); - openssh_bcrypt(passphrase, - bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds, - keybuf, sizeof(keybuf)); + ctx = aes_make_context(); + aes256_key(ctx, keybuf); + aes_iv(ctx, keybuf + 32); + aes_ssh2_sdctr(ctx, cpblob->u, + cpblob->len); + aes_free_context(ctx); - ctx = aes_make_context(); - aes256_key(ctx, keybuf); - aes_iv(ctx, keybuf + 32); - aes_ssh2_encrypt_blk(ctx, private_section_start, - p - private_section_start); - aes_free_context(ctx); + smemclr(keybuf, sizeof(keybuf)); + } - smemclr(keybuf, sizeof(keybuf)); + put_stringsb(cblob, cpblob); } /* * And save it. We'll use Unix line endings just in case it's * subsequently transferred in binary mode. */ - fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ + fp = f_open(filename, "wb", true); /* ensure Unix line endings */ if (!fp) goto error; fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp); - base64_encode(fp, outblob, p - outblob, 64); + base64_encode(fp, cblob->u, cblob->len, 64); fputs("-----END OPENSSH PRIVATE KEY-----\n", fp); fclose(fp); - ret = 1; + ret = true; error: - if (outblob) { - smemclr(outblob, maxsize); - sfree(outblob); - } - if (privblob) { - smemclr(privblob, privlen); - sfree(privblob); - } - if (pubblob) { - smemclr(pubblob, publen); - sfree(pubblob); - } + if (cblob) + strbuf_free(cblob); + if (privblob) + strbuf_free(privblob); + if (pubblob) + strbuf_free(pubblob); return ret; } @@ -1895,19 +1640,19 @@ int openssh_new_write(const Filename *filename, struct ssh2_userkey *key, * The switch function openssh_auto_write(), which chooses one of the * concrete OpenSSH output formats based on the key type. */ -int openssh_auto_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase) +static bool openssh_auto_write( + const Filename *filename, struct ssh2_userkey *key, const char *passphrase) { /* * The old OpenSSH format supports a fixed list of key types. We * assume that anything not in that fixed list is newer, and hence * will use the new format. */ - if (key->alg == &ssh_dss || - key->alg == &ssh_rsa || - key->alg == &ssh_ecdsa_nistp256 || - key->alg == &ssh_ecdsa_nistp384 || - key->alg == &ssh_ecdsa_nistp521) + if (ssh_key_alg(key->key) == &ssh_dss || + 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) return openssh_pem_write(filename, key, passphrase); else return openssh_new_write(filename, key, passphrase); @@ -2004,7 +1749,7 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename, int hdrstart, len; const char *errmsg; char *p; - int headers_done; + bool headers_done; char base64_bit[4]; int base64_chars = 0; @@ -2013,7 +1758,7 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename, ret->keyblob = NULL; ret->keyblob_len = ret->keyblob_size = 0; - fp = f_open(filename, "r", FALSE); + fp = f_open(filename, "r", false); if (!fp) { errmsg = "unable to open key file"; goto error; @@ -2031,7 +1776,7 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename, sfree(line); line = NULL; - headers_done = 0; + headers_done = false; while (1) { if (!(line = fgetline(fp))) { errmsg = "unexpected end of file"; @@ -2091,7 +1836,7 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename, ret->comment[sizeof(ret->comment)-1] = '\0'; } } else { - headers_done = 1; + headers_done = true; p = line; while (isbase64(*p)) { @@ -2156,39 +1901,28 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename, return NULL; } -int sshcom_encrypted(const Filename *filename, char **comment) +static bool sshcom_encrypted(const Filename *filename, char **comment) { struct sshcom_key *key = load_sshcom_key(filename, NULL); - int pos, len, answer; - - answer = 0; + BinarySource src[1]; + ptrlen str; + bool answer = false; *comment = NULL; if (!key) goto done; - /* - * Check magic number. - */ - if (GET_32BIT(key->keyblob) != 0x3f6ff9eb) { - goto done; /* key is invalid */ - } + BinarySource_BARE_INIT(src, key->keyblob, key->keyblob_len); - /* - * Find the cipher-type string. - */ - pos = 8; - if (key->keyblob_len < pos+4) - goto done; /* key is far too short */ - len = toint(GET_32BIT(key->keyblob + pos)); - if (len < 0 || len > key->keyblob_len - pos - 4) - goto done; /* key is far too short */ - pos += 4 + len; /* skip key type */ - len = toint(GET_32BIT(key->keyblob + pos)); /* find cipher-type length */ - if (len < 0 || len > key->keyblob_len - pos - 4) - goto done; /* cipher type string is incomplete */ - if (len != 4 || 0 != memcmp(key->keyblob + pos + 4, "none", 4)) - answer = 1; + if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) + goto done; /* key is invalid */ + get_uint32(src); /* skip length field */ + get_string(src); /* skip key type */ + str = get_string(src); /* cipher type */ + if (get_err(src)) + goto done; /* key is invalid */ + if (!ptrlen_eq_string(str, "none")) + answer = true; done: if (key) { @@ -2203,129 +1937,96 @@ int sshcom_encrypted(const Filename *filename, char **comment) return answer; } -static int sshcom_read_mpint(void *data, int len, struct mpint_pos *ret) +void BinarySink_put_mp_sshcom_from_string( + BinarySink *bs, const void *bytesv, int nbytes) { - unsigned bits, bytes; - unsigned char *d = (unsigned char *) data; - - if (len < 4) - goto error; - bits = GET_32BIT(d); - - bytes = (bits + 7) / 8; - if (len < 4+bytes) - goto error; - - ret->start = d + 4; - ret->bytes = bytes; - return bytes+4; - - error: - ret->start = NULL; - ret->bytes = -1; - return len; /* ensure further calls fail as well */ -} - -static int sshcom_put_mpint(void *target, void *data, int len) -{ - unsigned char *d = (unsigned char *)target; - unsigned char *i = (unsigned char *)data; - int bits = len * 8 - 1; + const unsigned char *bytes = (const unsigned char *)bytesv; + int bits = nbytes * 8 - 1; while (bits > 0) { - if (*i & (1 << (bits & 7))) + if (*bytes & (1 << (bits & 7))) break; if (!(bits-- & 7)) - i++, len--; + bytes++, nbytes--; } - PUT_32BIT(d, bits+1); - memcpy(d+4, i, len); - return len+4; + put_uint32(bs, bits+1); + put_data(bs, bytes, nbytes); } -struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, - const char **errmsg_p) +#define put_mp_sshcom_from_string(bs, val, len) \ + BinarySink_put_mp_sshcom_from_string(BinarySink_UPCAST(bs), val, len) + +static ptrlen BinarySource_get_mp_sshcom_as_string(BinarySource *src) +{ + unsigned bits = get_uint32(src); + return get_data(src, (bits + 7) / 8); +} + +#define get_mp_sshcom_as_string(bs) \ + BinarySource_get_mp_sshcom_as_string(BinarySource_UPCAST(bs)) + +static struct ssh2_userkey *sshcom_read( + const Filename *filename, const char *passphrase, const char **errmsg_p) { struct sshcom_key *key = load_sshcom_key(filename, errmsg_p); const char *errmsg; - int pos, len; + BinarySource src[1]; + ptrlen str, ciphertext; + int publen; const char prefix_rsa[] = "if-modn{sign{rsa"; const char prefix_dsa[] = "dl-modp{sign{dsa"; enum { RSA, DSA } type; - int encrypted; - char *ciphertext; - int cipherlen; + bool encrypted; struct ssh2_userkey *ret = NULL, *retkey; - const struct ssh_signkey *alg; - unsigned char *blob = NULL; - int blobsize = 0, publen, privlen; + const ssh_keyalg *alg; + strbuf *blob = NULL; if (!key) return NULL; - /* - * Check magic number. - */ - if (GET_32BIT(key->keyblob) != SSHCOM_MAGIC_NUMBER) { + BinarySource_BARE_INIT(src, key->keyblob, key->keyblob_len); + + if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) { errmsg = "key does not begin with magic number"; goto error; } + get_uint32(src); /* skip length field */ /* * Determine the key type. */ - pos = 8; - if (key->keyblob_len < pos+4 || - (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || - len > key->keyblob_len - pos - 4) { - errmsg = "key blob does not contain a key type string"; - goto error; - } - if (len > sizeof(prefix_rsa) - 1 && - !memcmp(key->keyblob+pos+4, prefix_rsa, sizeof(prefix_rsa) - 1)) { + str = get_string(src); + if (str.len > sizeof(prefix_rsa) - 1 && + !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) { type = RSA; - } else if (len > sizeof(prefix_dsa) - 1 && - !memcmp(key->keyblob+pos+4, prefix_dsa, sizeof(prefix_dsa) - 1)) { + } else if (str.len > sizeof(prefix_dsa) - 1 && + !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { type = DSA; } else { errmsg = "key is of unknown type"; goto error; } - pos += 4+len; /* * Determine the cipher type. */ - if (key->keyblob_len < pos+4 || - (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || - len > key->keyblob_len - pos - 4) { - errmsg = "key blob does not contain a cipher type string"; - goto error; - } - if (len == 4 && !memcmp(key->keyblob+pos+4, "none", 4)) - encrypted = 0; - else if (len == 8 && !memcmp(key->keyblob+pos+4, "3des-cbc", 8)) - encrypted = 1; + str = get_string(src); + if (ptrlen_eq_string(str, "none")) + encrypted = false; + else if (ptrlen_eq_string(str, "3des-cbc")) + encrypted = true; else { errmsg = "key encryption is of unknown type"; goto error; } - pos += 4+len; /* * Get hold of the encrypted part of the key. */ - if (key->keyblob_len < pos+4 || - (len = toint(GET_32BIT(key->keyblob + pos))) < 0 || - len > key->keyblob_len - pos - 4) { - errmsg = "key blob does not contain actual key data"; - goto error; - } - ciphertext = (char *)key->keyblob + pos + 4; - cipherlen = len; - if (cipherlen == 0) { - errmsg = "length of key data is zero"; + ciphertext = get_string(src); + if (ciphertext.len == 0) { + errmsg = "no key data found"; goto error; } @@ -2344,27 +2045,28 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, struct MD5Context md5c; unsigned char keybuf[32], iv[8]; - if (cipherlen % 8 != 0) { + if (ciphertext.len % 8 != 0) { errmsg = "encrypted part of key is not a multiple of cipher block" " size"; goto error; } MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + put_data(&md5c, passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); - MD5Update(&md5c, keybuf, 16); + put_data(&md5c, passphrase, strlen(passphrase)); + put_data(&md5c, keybuf, 16); MD5Final(keybuf+16, &md5c); /* - * Now decrypt the key blob. + * Now decrypt the key blob in place (casting away const from + * ciphertext being a ptrlen). */ memset(iv, 0, sizeof(iv)); - des3_decrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, - cipherlen); + des3_decrypt_pubkey_ossh(keybuf, iv, + (char *)ciphertext.ptr, ciphertext.len); smemclr(&md5c, sizeof(md5c)); smemclr(keybuf, sizeof(keybuf)); @@ -2380,87 +2082,80 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, } /* - * Strip away the containing string to get to the real meat. + * Expect the ciphertext to be formatted as a containing string, + * and reinitialise src to start parsing the inside of that string. */ - len = toint(GET_32BIT(ciphertext)); - if (len < 0 || len > cipherlen-4) { + BinarySource_BARE_INIT(src, ciphertext.ptr, ciphertext.len); + str = get_string(src); + if (get_err(src)) { errmsg = "containing string was ill-formed"; goto error; } - ciphertext += 4; - cipherlen = len; + BinarySource_BARE_INIT(src, str.ptr, str.len); /* * Now we break down into RSA versus DSA. In either case we'll * construct public and private blobs in our own format, and - * end up feeding them to alg->createkey(). + * end up feeding them to ssh_key_new_priv(). */ - blobsize = cipherlen + 256; - blob = snewn(blobsize, unsigned char); - privlen = 0; + blob = strbuf_new(); if (type == RSA) { - struct mpint_pos n, e, d, u, p, q; - int pos = 0; - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &e); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &d); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &n); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &u); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); - if (!q.start) { + ptrlen n, e, d, u, p, q; + + e = get_mp_sshcom_as_string(src); + d = get_mp_sshcom_as_string(src); + n = get_mp_sshcom_as_string(src); + u = get_mp_sshcom_as_string(src); + p = get_mp_sshcom_as_string(src); + q = get_mp_sshcom_as_string(src); + if (get_err(src)) { errmsg = "key data did not contain six integers"; goto error; } alg = &ssh_rsa; - pos = 0; - pos += put_string(blob+pos, "ssh-rsa", 7); - pos += put_mp(blob+pos, e.start, e.bytes); - pos += put_mp(blob+pos, n.start, n.bytes); - publen = pos; - pos += put_string(blob+pos, d.start, d.bytes); - pos += put_mp(blob+pos, q.start, q.bytes); - pos += put_mp(blob+pos, p.start, p.bytes); - pos += put_mp(blob+pos, u.start, u.bytes); - privlen = pos - publen; + put_stringz(blob, "ssh-rsa"); + put_mp_ssh2_from_string(blob, e.ptr, e.len); + put_mp_ssh2_from_string(blob, n.ptr, n.len); + publen = blob->len; + put_mp_ssh2_from_string(blob, d.ptr, d.len); + put_mp_ssh2_from_string(blob, q.ptr, q.len); + put_mp_ssh2_from_string(blob, p.ptr, p.len); + put_mp_ssh2_from_string(blob, u.ptr, u.len); } else { - struct mpint_pos p, q, g, x, y; - int pos = 4; + ptrlen p, q, g, x, y; assert(type == DSA); /* the only other option from the if above */ - if (GET_32BIT(ciphertext) != 0) { + if (get_uint32(src) != 0) { errmsg = "predefined DSA parameters not supported"; goto error; } - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &p); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &g); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &q); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &y); - pos += sshcom_read_mpint(ciphertext+pos, cipherlen-pos, &x); - if (!x.start) { + p = get_mp_sshcom_as_string(src); + g = get_mp_sshcom_as_string(src); + q = get_mp_sshcom_as_string(src); + y = get_mp_sshcom_as_string(src); + x = get_mp_sshcom_as_string(src); + if (get_err(src)) { errmsg = "key data did not contain five integers"; goto error; } alg = &ssh_dss; - pos = 0; - pos += put_string(blob+pos, "ssh-dss", 7); - pos += put_mp(blob+pos, p.start, p.bytes); - pos += put_mp(blob+pos, q.start, q.bytes); - pos += put_mp(blob+pos, g.start, g.bytes); - pos += put_mp(blob+pos, y.start, y.bytes); - publen = pos; - pos += put_mp(blob+pos, x.start, x.bytes); - privlen = pos - publen; + put_stringz(blob, "ssh-dss"); + put_mp_ssh2_from_string(blob, p.ptr, p.len); + put_mp_ssh2_from_string(blob, q.ptr, q.len); + put_mp_ssh2_from_string(blob, g.ptr, g.len); + put_mp_ssh2_from_string(blob, y.ptr, y.len); + publen = blob->len; + put_mp_ssh2_from_string(blob, x.ptr, x.len); } - assert(privlen > 0); /* should have bombed by now if not */ - retkey = snew(struct ssh2_userkey); - retkey->alg = alg; - retkey->data = alg->createkey(alg, blob, publen, blob+publen, privlen); - if (!retkey->data) { + retkey->key = ssh_key_new_priv( + alg, make_ptrlen(blob->u, publen), + make_ptrlen(blob->u + publen, blob->len - publen)); + if (!retkey->key) { sfree(retkey); errmsg = "unable to create key data structure"; goto error; @@ -2472,8 +2167,7 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, error: if (blob) { - smemclr(blob, blobsize); - sfree(blob); + strbuf_free(blob); } smemclr(key->keyblob, key->keyblob_size); sfree(key->keyblob); @@ -2483,50 +2177,51 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase, return ret; } -int sshcom_write(const Filename *filename, struct ssh2_userkey *key, - char *passphrase) +static bool sshcom_write( + const Filename *filename, struct ssh2_userkey *key, const char *passphrase) { - unsigned char *pubblob, *privblob; - int publen, privlen; - unsigned char *outblob; - int outlen; - struct mpint_pos numbers[6]; - int nnumbers, initial_zero, pos, lenpos, i; + strbuf *pubblob, *privblob, *outblob; + ptrlen numbers[6]; + int nnumbers, lenpos, i; + bool initial_zero; + BinarySource src[1]; const char *type; char *ciphertext; int cipherlen; - int ret = 0; + bool ret = false; FILE *fp; /* * Fetch the key blobs. */ - pubblob = key->alg->public_blob(key->data, &publen); - privblob = key->alg->private_blob(key->data, &privlen); + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new(); + ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); outblob = NULL; /* * Find the sequence of integers to be encoded into the OpenSSH * key blob, and also decide on the header line. */ - if (key->alg == &ssh_rsa) { - int pos; - struct mpint_pos n, e, d, p, q, iqmp; + if (ssh_key_alg(key->key) == &ssh_rsa) { + ptrlen n, e, d, p, q, iqmp; /* * These blobs were generated from inside PuTTY, so we needn't * treat them as untrusted. */ - pos = 4 + GET_32BIT(pubblob); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &e); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &n); - pos = 0; - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &d); - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &p); - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &q); - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &iqmp); - - assert(e.start && iqmp.start); /* can't go wrong */ + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + e = get_string(src); + n = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + d = get_string(src); + p = get_string(src); + q = get_string(src); + iqmp = get_string(src); + + assert(!get_err(src)); /* can't go wrong */ numbers[0] = e; numbers[1] = d; @@ -2536,25 +2231,25 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key, numbers[5] = p; nnumbers = 6; - initial_zero = 0; + initial_zero = false; type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; - } else if (key->alg == &ssh_dss) { - int pos; - struct mpint_pos p, q, g, y, x; + } else if (ssh_key_alg(key->key) == &ssh_dss) { + ptrlen p, q, g, y, x; /* * These blobs were generated from inside PuTTY, so we needn't * treat them as untrusted. */ - pos = 4 + GET_32BIT(pubblob); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &p); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &q); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &g); - pos += ssh2_read_mpint(pubblob+pos, publen-pos, &y); - pos = 0; - pos += ssh2_read_mpint(privblob+pos, privlen-pos, &x); + BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); + get_string(src); /* skip algorithm name */ + p = get_string(src); + q = get_string(src); + g = get_string(src); + y = get_string(src); + BinarySource_BARE_INIT(src, privblob->u, privblob->len); + x = get_string(src); - assert(y.start && x.start); /* can't go wrong */ + assert(!get_err(src)); /* can't go wrong */ numbers[0] = p; numbers[1] = g; @@ -2563,61 +2258,44 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key, numbers[4] = x; nnumbers = 5; - initial_zero = 1; + initial_zero = true; type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}"; } else { - assert(0); /* zoinks! */ - exit(1); /* XXX: GCC doesn't understand assert() on some systems. */ + goto error; /* unsupported key type */ } - /* - * Total size of key blob will be somewhere under 512 plus - * combined length of integers. We'll calculate the more - * precise size as we construct the blob. - */ - outlen = 512; - for (i = 0; i < nnumbers; i++) - outlen += 4 + numbers[i].bytes; - outblob = snewn(outlen, unsigned char); + outblob = strbuf_new(); /* * Create the unencrypted key blob. */ - pos = 0; - PUT_32BIT(outblob+pos, SSHCOM_MAGIC_NUMBER); pos += 4; - pos += 4; /* length field, fill in later */ - pos += put_string(outblob+pos, type, strlen(type)); - { - const char *ciphertype = passphrase ? "3des-cbc" : "none"; - pos += put_string(outblob+pos, ciphertype, strlen(ciphertype)); - } - lenpos = pos; /* remember this position */ - pos += 4; /* encrypted-blob size */ - pos += 4; /* encrypted-payload size */ - if (initial_zero) { - PUT_32BIT(outblob+pos, 0); - pos += 4; - } + put_uint32(outblob, SSHCOM_MAGIC_NUMBER); + put_uint32(outblob, 0); /* length field, fill in later */ + put_stringz(outblob, type); + put_stringz(outblob, passphrase ? "3des-cbc" : "none"); + lenpos = outblob->len; /* remember this position */ + put_uint32(outblob, 0); /* encrypted-blob size */ + put_uint32(outblob, 0); /* encrypted-payload size */ + if (initial_zero) + put_uint32(outblob, 0); for (i = 0; i < nnumbers; i++) - pos += sshcom_put_mpint(outblob+pos, - numbers[i].start, numbers[i].bytes); + put_mp_sshcom_from_string(outblob, numbers[i].ptr, numbers[i].len); /* Now wrap up the encrypted payload. */ - PUT_32BIT(outblob+lenpos+4, pos - (lenpos+8)); + PUT_32BIT(outblob->s + lenpos + 4, + outblob->len - (lenpos + 8)); /* Pad encrypted blob to a multiple of cipher block size. */ if (passphrase) { - int padding = -(pos - (lenpos+4)) & 7; + int padding = -(outblob->len - (lenpos+4)) & 7; while (padding--) - outblob[pos++] = random_byte(); + put_byte(outblob, random_byte()); } - ciphertext = (char *)outblob+lenpos+4; - cipherlen = pos - (lenpos+4); + ciphertext = outblob->s + lenpos + 4; + cipherlen = outblob->len - (lenpos + 4); assert(!passphrase || cipherlen % 8 == 0); /* Wrap up the encrypted blob string. */ - PUT_32BIT(outblob+lenpos, cipherlen); + PUT_32BIT(outblob->s + lenpos, cipherlen); /* And finally fill in the total length field. */ - PUT_32BIT(outblob+4, pos); - - assert(pos < outlen); + PUT_32BIT(outblob->s + 4, outblob->len); /* * Encrypt the key. @@ -2635,20 +2313,19 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key, unsigned char keybuf[32], iv[8]; MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + put_data(&md5c, passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); - MD5Update(&md5c, keybuf, 16); + put_data(&md5c, passphrase, strlen(passphrase)); + put_data(&md5c, keybuf, 16); MD5Final(keybuf+16, &md5c); /* * Now decrypt the key blob. */ memset(iv, 0, sizeof(iv)); - des3_encrypt_pubkey_ossh(keybuf, iv, (unsigned char *)ciphertext, - cipherlen); + des3_encrypt_pubkey_ossh(keybuf, iv, ciphertext, cipherlen); smemclr(&md5c, sizeof(md5c)); smemclr(keybuf, sizeof(keybuf)); @@ -2658,7 +2335,7 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key, * And save it. We'll use Unix line endings just in case it's * subsequently transferred in binary mode. */ - fp = f_open(filename, "wb", TRUE); /* ensure Unix line endings */ + fp = f_open(filename, "wb", true); /* ensure Unix line endings */ if (!fp) goto error; fputs("---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); @@ -2679,23 +2356,17 @@ int sshcom_write(const Filename *filename, struct ssh2_userkey *key, } fprintf(fp, "%s\"\n", c); } - base64_encode(fp, outblob, pos, 70); + base64_encode(fp, outblob->u, outblob->len, 70); fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); fclose(fp); - ret = 1; + ret = true; error: - if (outblob) { - smemclr(outblob, outlen); - sfree(outblob); - } - if (privblob) { - smemclr(privblob, privlen); - sfree(privblob); - } - if (pubblob) { - smemclr(pubblob, publen); - sfree(pubblob); - } + if (outblob) + strbuf_free(outblob); + if (privblob) + strbuf_free(privblob); + if (pubblob) + strbuf_free(pubblob); return ret; } diff --git a/int64.c b/int64.c deleted file mode 100644 index b1c986ce..00000000 --- a/int64.c +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Handling of the int64 and uint64 types. Done in 32-bit integers, - * for (pre-C99) portability. Hopefully once C99 becomes widespread - * we can kiss this lot goodbye... - */ - -#include -#include - -#include "int64.h" - -uint64 uint64_div10(uint64 x, int *remainder) -{ - uint64 y; - unsigned int rem, r2; - y.hi = x.hi / 10; - y.lo = x.lo / 10; - rem = x.lo % 10; - /* - * Now we have to add in the remainder left over from x.hi. - */ - r2 = x.hi % 10; - y.lo += r2 * 429496729; - rem += r2 * 6; - y.lo += rem / 10; - rem %= 10; - - if (remainder) - *remainder = rem; - return y; -} - -void uint64_decimal(uint64 x, char *buffer) -{ - char buf[20]; - int start = 20; - int d; - - do { - x = uint64_div10(x, &d); - assert(start > 0); - buf[--start] = d + '0'; - } while (x.hi || x.lo); - - memcpy(buffer, buf + start, sizeof(buf) - start); - buffer[sizeof(buf) - start] = '\0'; -} - -uint64 uint64_make(unsigned long hi, unsigned long lo) -{ - uint64 y; - y.hi = hi & 0xFFFFFFFFU; - y.lo = lo & 0xFFFFFFFFU; - return y; -} - -uint64 uint64_add(uint64 x, uint64 y) -{ - x.lo = (x.lo + y.lo) & 0xFFFFFFFFU; - x.hi += y.hi + (x.lo < y.lo ? 1 : 0); - return x; -} - -uint64 uint64_add32(uint64 x, unsigned long y) -{ - uint64 yy; - yy.hi = 0; - yy.lo = y; - return uint64_add(x, yy); -} - -int uint64_compare(uint64 x, uint64 y) -{ - if (x.hi != y.hi) - return x.hi < y.hi ? -1 : +1; - if (x.lo != y.lo) - return x.lo < y.lo ? -1 : +1; - return 0; -} - -uint64 uint64_subtract(uint64 x, uint64 y) -{ - x.lo = (x.lo - y.lo) & 0xFFFFFFFFU; - x.hi = (x.hi - y.hi - (x.lo > (y.lo ^ 0xFFFFFFFFU) ? 1 : 0)) & 0xFFFFFFFFU; - return x; -} - -double uint64_to_double(uint64 x) -{ - return (4294967296.0 * x.hi) + (double)x.lo; -} - -uint64 uint64_shift_right(uint64 x, int shift) -{ - if (shift < 32) { - x.lo >>= shift; - x.lo |= (x.hi << (32-shift)) & 0xFFFFFFFFU; - x.hi >>= shift; - } else { - x.lo = x.hi >> (shift-32); - x.hi = 0; - } - return x; -} - -uint64 uint64_shift_left(uint64 x, int shift) -{ - if (shift < 32) { - x.hi = (x.hi << shift) & 0xFFFFFFFFU; - x.hi |= (x.lo >> (32-shift)); - x.lo = (x.lo << shift) & 0xFFFFFFFFU; - } else { - x.hi = (x.lo << (shift-32)) & 0xFFFFFFFFU; - x.lo = 0; - } - return x; -} - -uint64 uint64_from_decimal(char *str) -{ - uint64 ret; - ret.hi = ret.lo = 0; - while (*str >= '0' && *str <= '9') { - ret = uint64_add(uint64_shift_left(ret, 3), - uint64_shift_left(ret, 1)); - ret = uint64_add32(ret, *str - '0'); - str++; - } - return ret; -} - -#ifdef TESTMODE - -#include - -int main(void) -{ - uint64 x, y, z; - char buf[80]; - - x = uint64_make(0x3456789AUL, 0xDEF01234UL); - printf("%08lx.%08lx\n", x.hi, x.lo); - uint64_decimal(x, buf); - printf("%s\n", buf); - - y = uint64_add32(x, 0xFFFFFFFFU); - printf("%08lx.%08lx\n", y.hi, y.lo); - uint64_decimal(y, buf); - printf("%s\n", buf); - - z = uint64_subtract(y, x); - printf("%08lx.%08lx\n", z.hi, z.lo); - uint64_decimal(z, buf); - printf("%s\n", buf); - - z = uint64_subtract(x, y); - printf("%08lx.%08lx\n", z.hi, z.lo); - uint64_decimal(z, buf); - printf("%s\n", buf); - - y = uint64_shift_right(x, 4); - printf("%08lx.%08lx\n", y.hi, y.lo); - - y = uint64_shift_right(x, 36); - printf("%08lx.%08lx\n", y.hi, y.lo); - - y = uint64_shift_left(x, 4); - printf("%08lx.%08lx\n", x.hi, x.lo); - - y = uint64_shift_left(x, 36); - printf("%08lx.%08lx\n", x.hi, x.lo); - - return 0; -} -#endif diff --git a/int64.h b/int64.h deleted file mode 100644 index f62f8efc..00000000 --- a/int64.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Header for int64.c. - */ - -#ifndef PUTTY_INT64_H -#define PUTTY_INT64_H - -typedef struct { - unsigned long hi, lo; -} uint64; - -uint64 uint64_div10(uint64 x, int *remainder); -void uint64_decimal(uint64 x, char *buffer); -uint64 uint64_make(unsigned long hi, unsigned long lo); -uint64 uint64_add(uint64 x, uint64 y); -uint64 uint64_add32(uint64 x, unsigned long y); -int uint64_compare(uint64 x, uint64 y); -uint64 uint64_subtract(uint64 x, uint64 y); -double uint64_to_double(uint64 x); -uint64 uint64_shift_right(uint64 x, int shift); -uint64 uint64_shift_left(uint64 x, int shift); -uint64 uint64_from_decimal(char *str); - -#endif diff --git a/ldisc.c b/ldisc.c index 320a9360..0779d142 100644 --- a/ldisc.c +++ b/ldisc.c @@ -15,19 +15,19 @@ #define ECHOING (ldisc->localecho == FORCE_ON || \ (ldisc->localecho == AUTO && \ - (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \ + (backend_ldisc_option_state(ldisc->backend, LD_ECHO) || \ term_ldisc(ldisc->term, LD_ECHO)))) #define EDITING (ldisc->localedit == FORCE_ON || \ (ldisc->localedit == AUTO && \ - (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \ + (backend_ldisc_option_state(ldisc->backend, LD_EDIT) || \ term_ldisc(ldisc->term, LD_EDIT)))) -static void c_write(Ldisc ldisc, const char *buf, int len) +static void c_write(Ldisc *ldisc, const void *buf, int len) { - from_backend(ldisc->frontend, 0, buf, len); + seat_stdout(ldisc->seat, buf, len); } -static int plen(Ldisc ldisc, unsigned char c) +static int plen(Ldisc *ldisc, unsigned char c) { if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) return 1; @@ -42,12 +42,12 @@ static int plen(Ldisc ldisc, unsigned char c) return 4; /* hex representation */ } -static void pwrite(Ldisc ldisc, unsigned char c) +static void pwrite(Ldisc *ldisc, unsigned char c) { if ((c >= 32 && c <= 126) || (!in_utf(ldisc->term) && c >= 0xA0) || (in_utf(ldisc->term) && c >= 0x80)) { - c_write(ldisc, (char *)&c, 1); + c_write(ldisc, &c, 1); } else if (c < 128) { char cc[2]; cc[1] = (c == 127 ? '?' : c + 0x40); @@ -60,15 +60,15 @@ static void pwrite(Ldisc ldisc, unsigned char c) } } -static int char_start(Ldisc ldisc, unsigned char c) +static bool char_start(Ldisc *ldisc, unsigned char c) { if (in_utf(ldisc->term)) return (c < 0x80 || c >= 0xC0); else - return 1; + return true; } -static void bsb(Ldisc ldisc, int n) +static void bsb(Ldisc *ldisc, int n) { while (n--) c_write(ldisc, "\010 \010", 3); @@ -77,78 +77,63 @@ static void bsb(Ldisc ldisc, int n) #define CTRL(x) (x^'@') #define KCTRL(x) ((x^'@') | 0x100) -void *ldisc_create(Conf *conf, Terminal *term, - Backend *back, void *backhandle, - void *frontend) +Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) { - Ldisc ldisc = snew(struct ldisc_tag); + Ldisc *ldisc = snew(Ldisc); ldisc->buf = NULL; ldisc->buflen = 0; ldisc->bufsiz = 0; - ldisc->quotenext = 0; + ldisc->quotenext = false; - ldisc->back = back; - ldisc->backhandle = backhandle; + ldisc->backend = backend; ldisc->term = term; - ldisc->frontend = frontend; + ldisc->seat = seat; ldisc_configure(ldisc, conf); /* Link ourselves into the backend and the terminal */ if (term) term->ldisc = ldisc; - if (back) - back->provide_ldisc(backhandle, ldisc); + if (backend) + backend_provide_ldisc(backend, ldisc); return ldisc; } -void ldisc_configure(void *handle, Conf *conf) +void ldisc_configure(Ldisc *ldisc, Conf *conf) { - Ldisc ldisc = (Ldisc) handle; - - ldisc->telnet_keyboard = conf_get_int(conf, CONF_telnet_keyboard); - ldisc->telnet_newline = conf_get_int(conf, CONF_telnet_newline); + ldisc->telnet_keyboard = conf_get_bool(conf, CONF_telnet_keyboard); + ldisc->telnet_newline = conf_get_bool(conf, CONF_telnet_newline); ldisc->protocol = conf_get_int(conf, CONF_protocol); ldisc->localecho = conf_get_int(conf, CONF_localecho); ldisc->localedit = conf_get_int(conf, CONF_localedit); } -void ldisc_free(void *handle) +void ldisc_free(Ldisc *ldisc) { - Ldisc ldisc = (Ldisc) handle; - if (ldisc->term) ldisc->term->ldisc = NULL; - if (ldisc->back) - ldisc->back->provide_ldisc(ldisc->backhandle, NULL); + if (ldisc->backend) + backend_provide_ldisc(ldisc->backend, NULL); if (ldisc->buf) sfree(ldisc->buf); sfree(ldisc); } -void ldisc_echoedit_update(void *handle) +void ldisc_echoedit_update(Ldisc *ldisc) { - Ldisc ldisc = (Ldisc) handle; - frontend_echoedit_update(ldisc->frontend, ECHOING, EDITING); + seat_echoedit_update(ldisc->seat, ECHOING, EDITING); } -void ldisc_send(void *handle, const char *buf, int len, int interactive) +void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) { - Ldisc ldisc = (Ldisc) handle; + const char *buf = (const char *)vbuf; int keyflag = 0; assert(ldisc->term); assert(len); - /* - * Notify the front end that something was pressed, in case - * it's depending on finding out (e.g. keypress termination for - * Close On Exit). - */ - frontend_keypress(ldisc->frontend); - if (interactive) { /* * Interrupt a paste from the clipboard, if one was in @@ -224,7 +209,7 @@ void ldisc_send(void *handle, const char *buf, int len, int interactive) bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); ldisc->buflen--; } - ldisc->back->special(ldisc->backhandle, TS_EL); + backend_special(ldisc->backend, SS_EL, 0); /* * We don't send IP, SUSP or ABORT if the user has * configured telnet specials off! This breaks @@ -233,11 +218,11 @@ void ldisc_send(void *handle, const char *buf, int len, int interactive) if (!ldisc->telnet_keyboard) goto default_case; if (c == CTRL('C')) - ldisc->back->special(ldisc->backhandle, TS_IP); + backend_special(ldisc->backend, SS_IP, 0); if (c == CTRL('Z')) - ldisc->back->special(ldisc->backhandle, TS_SUSP); + backend_special(ldisc->backend, SS_SUSP, 0); if (c == CTRL('\\')) - ldisc->back->special(ldisc->backhandle, TS_ABORT); + backend_special(ldisc->backend, SS_ABORT, 0); break; case CTRL('R'): /* redraw line */ if (ECHOING) { @@ -248,13 +233,13 @@ void ldisc_send(void *handle, const char *buf, int len, int interactive) } break; case CTRL('V'): /* quote next char */ - ldisc->quotenext = TRUE; + ldisc->quotenext = true; break; case CTRL('D'): /* logout or send */ if (ldisc->buflen == 0) { - ldisc->back->special(ldisc->backhandle, TS_EOF); + backend_special(ldisc->backend, SS_EOF, 0); } else { - ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + backend_send(ldisc->backend, ldisc->buf, ldisc->buflen); ldisc->buflen = 0; } break; @@ -290,13 +275,14 @@ void ldisc_send(void *handle, const char *buf, int len, int interactive) /* FALLTHROUGH */ case KCTRL('M'): /* send with newline */ if (ldisc->buflen > 0) - ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + backend_send(ldisc->backend, + ldisc->buf, ldisc->buflen); if (ldisc->protocol == PROT_RAW) - ldisc->back->send(ldisc->backhandle, "\r\n", 2); + backend_send(ldisc->backend, "\r\n", 2); else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - ldisc->back->special(ldisc->backhandle, TS_EOL); + backend_special(ldisc->backend, SS_EOL, 0); else - ldisc->back->send(ldisc->backhandle, "\r", 1); + backend_send(ldisc->backend, "\r", 1); if (ECHOING) c_write(ldisc, "\r\n", 2); ldisc->buflen = 0; @@ -312,13 +298,13 @@ void ldisc_send(void *handle, const char *buf, int len, int interactive) ldisc->buf[ldisc->buflen++] = c; if (ECHOING) pwrite(ldisc, (unsigned char) c); - ldisc->quotenext = FALSE; + ldisc->quotenext = false; break; } } } else { if (ldisc->buflen != 0) { - ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); + backend_send(ldisc->backend, ldisc->buf, ldisc->buflen); while (ldisc->buflen > 0) { bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); ldisc->buflen--; @@ -331,33 +317,33 @@ void ldisc_send(void *handle, const char *buf, int len, int interactive) switch (buf[0]) { case CTRL('M'): if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - ldisc->back->special(ldisc->backhandle, TS_EOL); + backend_special(ldisc->backend, SS_EOL, 0); else - ldisc->back->send(ldisc->backhandle, "\r", 1); + backend_send(ldisc->backend, "\r", 1); break; case CTRL('?'): case CTRL('H'): if (ldisc->telnet_keyboard) { - ldisc->back->special(ldisc->backhandle, TS_EC); + backend_special(ldisc->backend, SS_EC, 0); break; } case CTRL('C'): if (ldisc->telnet_keyboard) { - ldisc->back->special(ldisc->backhandle, TS_IP); + backend_special(ldisc->backend, SS_IP, 0); break; } case CTRL('Z'): if (ldisc->telnet_keyboard) { - ldisc->back->special(ldisc->backhandle, TS_SUSP); + backend_special(ldisc->backend, SS_SUSP, 0); break; } default: - ldisc->back->send(ldisc->backhandle, buf, len); + backend_send(ldisc->backend, buf, len); break; } } else - ldisc->back->send(ldisc->backhandle, buf, len); + backend_send(ldisc->backend, buf, len); } } } diff --git a/ldisc.h b/ldisc.h index 5dbe2a76..65a544ad 100644 --- a/ldisc.h +++ b/ldisc.h @@ -8,19 +8,20 @@ #ifndef PUTTY_LDISC_H #define PUTTY_LDISC_H -typedef struct ldisc_tag { +struct Ldisc_tag { Terminal *term; - Backend *back; - void *backhandle; - void *frontend; + Backend *backend; + Seat *seat; /* * Values cached out of conf. */ - int telnet_keyboard, telnet_newline, protocol, localecho, localedit; + bool telnet_keyboard, telnet_newline; + int protocol, localecho, localedit; char *buf; - int buflen, bufsiz, quotenext; -} *Ldisc; + int buflen, bufsiz; + bool quotenext; +}; #endif /* PUTTY_LDISC_H */ diff --git a/ldiscucs.c b/ldiscucs.c index 1634bc43..774368f3 100644 --- a/ldiscucs.c +++ b/ldiscucs.c @@ -12,10 +12,9 @@ #include "terminal.h" #include "ldisc.h" -void lpage_send(void *handle, - int codepage, const char *buf, int len, int interactive) +void lpage_send(Ldisc *ldisc, + int codepage, const char *buf, int len, bool interactive) { - Ldisc ldisc = (Ldisc)handle; wchar_t *widebuffer = 0; int widesize = 0; int wclen; @@ -34,9 +33,8 @@ void lpage_send(void *handle, sfree(widebuffer); } -void luni_send(void *handle, const wchar_t *widebuf, int len, int interactive) +void luni_send(Ldisc *ldisc, const wchar_t *widebuf, int len, bool interactive) { - Ldisc ldisc = (Ldisc)handle; int ratio = (in_utf(ldisc->term))?3:1; char *linebuffer; int linesize; @@ -86,7 +84,7 @@ void luni_send(void *handle, const wchar_t *widebuf, int len, int interactive) } else { int rv; rv = wc_to_mb(ldisc->term->ucsdata->line_codepage, 0, widebuf, len, - linebuffer, linesize, NULL, NULL, ldisc->term->ucsdata); + linebuffer, linesize, NULL, ldisc->term->ucsdata); if (rv >= 0) p = linebuffer + rv; else diff --git a/logging.c b/logging.c index 865fe9b8..a43ebcd1 100644 --- a/logging.c +++ b/logging.c @@ -17,7 +17,7 @@ struct LogContext { enum { L_CLOSED, L_OPENING, L_OPEN, L_ERROR } state; bufchain queue; Filename *currlogfilename; - void *frontend; + LogPolicy *lp; Conf *conf; int logtype; /* cached out of conf */ }; @@ -31,7 +31,7 @@ static Filename *xlatlognam(Filename *s, char *hostname, int port, * isn't open, buffering data if it's in the process of being * opened asynchronously, etc. */ -static void logwrite(struct LogContext *ctx, void *data, int len) +static void logwrite(LogContext *ctx, void *data, int len) { /* * In state L_CLOSED, we call logfopen, which will set the state @@ -48,9 +48,8 @@ static void logwrite(struct LogContext *ctx, void *data, int len) if (fwrite(data, 1, len, ctx->lgfp) < (size_t)len) { logfclose(ctx); ctx->state = L_ERROR; - /* Log state is L_ERROR so this won't cause a loop */ - logevent(ctx->frontend, - "Disabled writing session log due to error while writing"); + lp_eventlog(ctx->lp, "Disabled writing session log " + "due to error while writing"); } } /* else L_ERROR, so ignore the write */ } @@ -59,7 +58,7 @@ static void logwrite(struct LogContext *ctx, void *data, int len) * Convenience wrapper on logwrite() which printf-formats the * string. */ -static void logprintf(struct LogContext *ctx, const char *fmt, ...) +static void logprintf(LogContext *ctx, const char *fmt, ...) { va_list ap; char *data; @@ -75,35 +74,35 @@ static void logprintf(struct LogContext *ctx, const char *fmt, ...) /* * Flush any open log file. */ -void logflush(void *handle) { - struct LogContext *ctx = (struct LogContext *)handle; +void logflush(LogContext *ctx) +{ if (ctx->logtype > 0) if (ctx->state == L_OPEN) fflush(ctx->lgfp); } -static void logfopen_callback(void *handle, int mode) +static void logfopen_callback(void *vctx, int mode) { - struct LogContext *ctx = (struct LogContext *)handle; + LogContext *ctx = (LogContext *)vctx; char buf[256], *event; struct tm tm; const char *fmode; - int shout = FALSE; + bool shout = false; if (mode == 0) { ctx->state = L_ERROR; /* disable logging */ } else { fmode = (mode == 1 ? "ab" : "wb"); - ctx->lgfp = f_open(ctx->currlogfilename, fmode, FALSE); + ctx->lgfp = f_open(ctx->currlogfilename, fmode, false); if (ctx->lgfp) { ctx->state = L_OPEN; } else { ctx->state = L_ERROR; - shout = TRUE; + shout = true; } } - if (ctx->state == L_OPEN) { + if (ctx->state == L_OPEN && conf_get_bool(ctx->conf, CONF_logheader)) { /* Write header line into log file. */ tm = ltime(); strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm); @@ -121,23 +120,14 @@ static void logfopen_callback(void *handle, int mode) ctx->logtype == LGTYP_SSHRAW ? "SSH raw data" : "unknown"), filename_to_str(ctx->currlogfilename)); - logevent(ctx->frontend, event); + lp_eventlog(ctx->lp, event); if (shout) { /* * If we failed to open the log file due to filesystem error * (as opposed to user action such as clicking Cancel in the - * askappend box), we should log it more prominently. We do - * this by sending it to the same place that stderr output - * from the main session goes (so, either a console tool's - * actual stderr, or a terminal window). - * - * Of course this is one case in which that policy won't cause - * it to turn up embarrassingly in a log file of real server - * output, because the whole point is that we haven't managed - * to open any such log file :-) + * askappend box), we should log it more prominently. */ - from_backend(ctx->frontend, 1, event, strlen(event)); - from_backend(ctx->frontend, 1, "\r\n", 2); + lp_logging_error(ctx->lp, event); } sfree(event); @@ -153,6 +143,7 @@ static void logfopen_callback(void *handle, int mode) logwrite(ctx, data, len); bufchain_consume(&ctx->queue, len); } + logflush(ctx); } /* @@ -160,11 +151,9 @@ static void logfopen_callback(void *handle, int mode) * file and asking the user whether they want to append, overwrite * or cancel logging. */ -void logfopen(void *handle) +void logfopen(LogContext *ctx) { - struct LogContext *ctx = (struct LogContext *)handle; struct tm tm; - FILE *fp; int mode; /* Prevent repeat calls */ @@ -184,15 +173,13 @@ void logfopen(void *handle) conf_get_str(ctx->conf, CONF_host), conf_get_int(ctx->conf, CONF_port), &tm); - fp = f_open(ctx->currlogfilename, "r", FALSE); /* file already present? */ - if (fp) { + if (open_for_write_would_lose_data(ctx->currlogfilename)) { int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr); - fclose(fp); if (logxfovr != LGXF_ASK) { mode = ((logxfovr == LGXF_OVR) ? 2 : 1); } else - mode = askappend(ctx->frontend, ctx->currlogfilename, - logfopen_callback, ctx); + mode = lp_askappend(ctx->lp, ctx->currlogfilename, + logfopen_callback, ctx); } else mode = 2; /* create == overwrite */ @@ -202,9 +189,8 @@ void logfopen(void *handle) logfopen_callback(ctx, mode); /* open the file */ } -void logfclose(void *handle) +void logfclose(LogContext *ctx) { - struct LogContext *ctx = (struct LogContext *)handle; if (ctx->lgfp) { fclose(ctx->lgfp); ctx->lgfp = NULL; @@ -215,39 +201,73 @@ void logfclose(void *handle) /* * Log session traffic. */ -void logtraffic(void *handle, unsigned char c, int logmode) +void logtraffic(LogContext *ctx, unsigned char c, int logmode) { - struct LogContext *ctx = (struct LogContext *)handle; if (ctx->logtype > 0) { if (ctx->logtype == logmode) logwrite(ctx, &c, 1); } } -/* - * Log an Event Log entry. Used in SSH packet logging mode; this is - * also as convenient a place as any to put the output of Event Log - * entries to stderr when a command-line tool is in verbose mode. - * (In particular, this is a better place to put it than in the - * front ends, because it only has to be done once for all - * platforms. Platforms which don't have a meaningful stderr can - * just avoid defining FLAG_STDERR. - */ -void log_eventlog(void *handle, const char *event) +static void logevent_internal(LogContext *ctx, const char *event) { - struct LogContext *ctx = (struct LogContext *)handle; - if ((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)) { - fprintf(stderr, "%s\n", event); - fflush(stderr); + if (ctx->logtype == LGTYP_PACKETS || ctx->logtype == LGTYP_SSHRAW) { + logprintf(ctx, "Event Log: %s\r\n", event); + logflush(ctx); } - /* If we don't have a context yet (eg winnet.c init) then skip entirely */ + lp_eventlog(ctx->lp, event); +} + +void logevent(LogContext *ctx, const char *event) +{ if (!ctx) - return; - if (ctx->logtype != LGTYP_PACKETS && - ctx->logtype != LGTYP_SSHRAW) - return; - logprintf(ctx, "Event Log: %s\r\n", event); - logflush(ctx); + return; + + /* + * Replace newlines in Event Log messages with spaces. (Sometimes + * the same message string is reused for the Event Log and a GUI + * dialog box; newlines are sometimes appropriate in the latter, + * but never in the former.) + */ + if (strchr(event, '\n') || strchr(event, '\r')) { + char *dup = dupstr(event); + char *p = dup, *q = dup; + while (*p) { + if (*p == '\r' || *p == '\n') { + do { + p++; + } while (*p == '\r' || *p == '\n'); + *q++ = ' '; + } else { + *q++ = *p++; + } + } + *q = '\0'; + logevent_internal(ctx, dup); + sfree(dup); + } else { + logevent_internal(ctx, event); + } +} + +void logevent_and_free(LogContext *ctx, char *event) +{ + logevent(ctx, event); + sfree(event); +} + +void logeventvf(LogContext *ctx, const char *fmt, va_list ap) +{ + logevent_and_free(ctx, dupvprintf(fmt, ap)); +} + +void logeventf(LogContext *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + logeventvf(ctx, fmt, ap); + va_end(ap); } /* @@ -255,13 +275,12 @@ void log_eventlog(void *handle, const char *event) * If n_blanks != 0, blank or omit some parts. * Set of blanking areas must be in increasing order. */ -void log_packet(void *handle, int direction, int type, +void log_packet(LogContext *ctx, int direction, int type, const char *texttype, const void *data, int len, int n_blanks, const struct logblank_t *blanks, const unsigned long *seq, unsigned downstream_id, const char *additional_log_text) { - struct LogContext *ctx = (struct LogContext *)handle; char dumpdata[80], smalldata[5]; int p = 0, b = 0, omitted = 0; int output_pos = 0; /* NZ if pending output in dumpdata */ @@ -351,7 +370,7 @@ void log_packet(void *handle, int direction, int type, } dumpdata[10+2+3*(p%16)] = smalldata[0]; dumpdata[10+2+3*(p%16)+1] = smalldata[1]; - dumpdata[10+1+3*16+2+(p%16)] = (isprint(c) ? c : '.'); + dumpdata[10+1+3*16+2+(p%16)] = (c >= 0x20 && c < 0x7F ? c : '.'); output_pos = (p%16) + 1; } @@ -375,12 +394,12 @@ void log_packet(void *handle, int direction, int type, logflush(ctx); } -void *log_init(void *frontend, Conf *conf) +LogContext *log_init(LogPolicy *lp, Conf *conf) { - struct LogContext *ctx = snew(struct LogContext); + LogContext *ctx = snew(LogContext); ctx->lgfp = NULL; ctx->state = L_CLOSED; - ctx->frontend = frontend; + ctx->lp = lp; ctx->conf = conf_copy(conf); ctx->logtype = conf_get_int(ctx->conf, CONF_logtype); ctx->currlogfilename = NULL; @@ -388,10 +407,8 @@ void *log_init(void *frontend, Conf *conf) return ctx; } -void log_free(void *handle) +void log_free(LogContext *ctx) { - struct LogContext *ctx = (struct LogContext *)handle; - logfclose(ctx); bufchain_clear(&ctx->queue); if (ctx->currlogfilename) @@ -400,18 +417,17 @@ void log_free(void *handle) sfree(ctx); } -void log_reconfig(void *handle, Conf *conf) +void log_reconfig(LogContext *ctx, Conf *conf) { - struct LogContext *ctx = (struct LogContext *)handle; - int reset_logging; + bool reset_logging; if (!filename_equal(conf_get_filename(ctx->conf, CONF_logfilename), conf_get_filename(conf, CONF_logfilename)) || conf_get_int(ctx->conf, CONF_logtype) != conf_get_int(conf, CONF_logtype)) - reset_logging = TRUE; + reset_logging = true; else - reset_logging = FALSE; + reset_logging = false; if (reset_logging) logfclose(ctx); @@ -447,7 +463,7 @@ static Filename *xlatlognam(Filename *src, char *hostname, int port, s = filename_to_str(src); while (*s) { - int sanitise = FALSE; + bool sanitise = false; /* Let (bufp, len) be the string to append. */ bufp = buf; /* don't usually override this */ if (*s == '&') { @@ -485,7 +501,7 @@ static Filename *xlatlognam(Filename *src, char *hostname, int port, * auto-format directives. E.g. 'hostname' can contain * colons, if it's an IPv6 address, and colons aren't * legal in filenames on Windows. */ - sanitise = TRUE; + sanitise = true; } else { buf[0] = *s++; size = 1; diff --git a/mainchan.c b/mainchan.c new file mode 100644 index 00000000..ce991933 --- /dev/null +++ b/mainchan.c @@ -0,0 +1,558 @@ +/* + * 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 int mainchan_send(Channel *chan, bool is_stderr, const void *, int); +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 struct ChannelVtable mainchan_channelvt = { + mainchan_free, + mainchan_open_confirmation, + mainchan_open_failure, + mainchan_send, + mainchan_send_eof, + mainchan_set_input_wanted, + mainchan_log_close_msg, + chan_default_want_close, + mainchan_rcvd_exit_status, + mainchan_rcvd_exit_signal, + mainchan_rcvd_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + 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")); + ssh_enable_agent_fwd(mc->cl); + } 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); +} + +struct mainchan_open_failure_abort_ctx { + Ssh *ssh; + char *abort_message; +}; + +static void mainchan_open_failure_abort(void *vctx) +{ + struct mainchan_open_failure_abort_ctx *ctx = + (struct mainchan_open_failure_abort_ctx *)vctx; + ssh_sw_abort( + ctx->ssh, "Server refused to open main channel: %s", + ctx->abort_message); + sfree(ctx->abort_message); + sfree(ctx); +} + +static void mainchan_open_failure(Channel *chan, const char *errtext) +{ + assert(chan->vt == &mainchan_channelvt); + mainchan *mc = container_of(chan, mainchan, chan); + + struct mainchan_open_failure_abort_ctx *ctx = + snew(struct mainchan_open_failure_abort_ctx); + + ctx->ssh = mc->ppl->ssh; + ctx->abort_message = dupstr(errtext); + queue_toplevel_callback(mainchan_open_failure_abort, ctx); +} + +static int mainchan_send(Channel *chan, bool is_stderr, + const void *data, int 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); /* now 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/marshal.c b/marshal.c new file mode 100644 index 00000000..06f6d631 --- /dev/null +++ b/marshal.c @@ -0,0 +1,231 @@ +#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_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; +} + +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); +} diff --git a/marshal.h b/marshal.h new file mode 100644 index 00000000..e19cd0b9 --- /dev/null +++ b/marshal.h @@ -0,0 +1,285 @@ +#ifndef PUTTY_MARSHAL_H +#define PUTTY_MARSHAL_H + +#include "defs.h" + +/* + * A sort of 'abstract base class' or 'interface' or 'trait' which is + * the common feature of all types that want to accept data formatted + * using the SSH binary conventions of uint32, string, mpint etc. + */ +struct BinarySink { + void (*write)(BinarySink *sink, const void *data, size_t len); + BinarySink *binarysink_; +}; + +/* + * To define a structure type as a valid target for binary formatted + * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when + * an instance is set up, use 'BinarySink_INIT' to initialise the + * 'base class' state, providing a function pointer to be the + * implementation of the write() call above. + */ +#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1] +#define BinarySink_INIT(obj, writefn) \ + ((obj)->binarysink_->write = (writefn), \ + (obj)->binarysink_->binarysink_ = (obj)->binarysink_) + +/* + * To define a larger structure type as a valid BinarySink in such a + * way that it will delegate the write method to some other object, + * put 'BinarySink_DELEGATE_IMPLEMENTATION' in its declaration, and + * when an instance is set up, use 'BinarySink_DELEGATE_INIT' to point + * at the object it wants to delegate to. + */ +#define BinarySink_DELEGATE_IMPLEMENTATION BinarySink *binarysink_ +#define BinarySink_DELEGATE_INIT(obj, othersink) \ + ((obj)->binarysink_ = BinarySink_UPCAST(othersink)) + +/* + * The implementing type's write function will want to downcast its + * 'BinarySink *' parameter back to the more specific type. Also, + * sometimes you'll want to upcast a pointer to a particular + * implementing type into an abstract 'BinarySink *' to pass to + * generic subroutines not defined in this file. These macros do that + * job. + * + * Importantly, BinarySink_UPCAST can also be applied to a BinarySink + * * itself (and leaves it unchanged). That's achieved by a small + * piece of C trickery: implementing structures and the BinarySink + * structure itself both contain a field called binarysink_, but in + * implementing objects it's a BinarySink[1] whereas in the abstract + * type it's a 'BinarySink *' pointing back to the same structure, + * meaning that you can say 'foo->binarysink_' in either case and get + * a pointer type by different methods. + */ +#define BinarySink_DOWNCAST(object, type) \ + TYPECHECK((object) == ((type *)0)->binarysink_, \ + ((type *)(((char *)(object)) - offsetof(type, binarysink_)))) +#define BinarySink_UPCAST(object) \ + TYPECHECK((object)->binarysink_ == (BinarySink *)0, \ + (object)->binarysink_) + +/* + * If you structure-copy an object that's implementing BinarySink, + * then that tricky self-pointer in its trait subobject will point to + * the wrong place. You could call BinarySink_INIT again, but this + * macro is terser and does all that's needed to fix up the copied + * object. + */ +#define BinarySink_COPIED(obj) \ + ((obj)->binarysink_->binarysink_ = (obj)->binarysink_) + +/* + * The put_* macros are the main client to this system. Any structure + * which implements the BinarySink 'trait' is valid for use as the + * first parameter of any of these put_* macros. + */ + +/* Basic big-endian integer types. */ +#define put_byte(bs, val) \ + BinarySink_put_byte(BinarySink_UPCAST(bs), val) +#define put_uint16(bs, val) \ + BinarySink_put_uint16(BinarySink_UPCAST(bs), val) +#define put_uint32(bs, val) \ + BinarySink_put_uint32(BinarySink_UPCAST(bs), val) +#define put_uint64(bs, val) \ + BinarySink_put_uint64(BinarySink_UPCAST(bs), val) + +/* SSH booleans, encoded as a single byte storing either 0 or 1. */ +#define put_bool(bs, val) \ + BinarySink_put_bool(BinarySink_UPCAST(bs), val) + +/* SSH strings, with a leading uint32 length field. 'stringz' is a + * convenience function that takes an ordinary C zero-terminated + * string as input. 'stringsb' takes a strbuf * as input, and + * finalises it as a side effect (handy for multi-level marshalling in + * which you use these same functions to format an inner blob of data + * that then gets wrapped into a string container in an outer one). */ +#define put_string(bs, val, len) \ + BinarySink_put_string(BinarySink_UPCAST(bs),val,len) +#define put_stringpl(bs, ptrlen) \ + BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen) +#define put_stringz(bs, val) \ + BinarySink_put_stringz(BinarySink_UPCAST(bs), val) +#define put_stringsb(bs, val) \ + BinarySink_put_stringsb(BinarySink_UPCAST(bs), val) + +/* Other string outputs: 'asciz' emits the string data directly into + * the output including the terminating \0, and 'pstring' emits the + * string in Pascal style with a leading _one_-byte length field. + * pstring can fail if the string is too long. */ +#define put_asciz(bs, val) \ + BinarySink_put_asciz(BinarySink_UPCAST(bs), val) +#define put_pstring(bs, val) \ + BinarySink_put_pstring(BinarySink_UPCAST(bs), val) + +/* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */ +#define put_mp_ssh1(bs, val) \ + BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val) +#define put_mp_ssh2(bs, val) \ + BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val) + +/* Padding with a specified byte. */ +#define put_padding(bs, len, padbyte) \ + BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte) + +/* Fallback: just emit raw data bytes, using a syntax that matches the + * rest of these macros. */ +#define put_data(bs, val, len) \ + BinarySink_put_data(BinarySink_UPCAST(bs), val, len) + +/* + * The underlying real C functions that implement most of those + * macros. Generally you won't want to call these directly, because + * they have such cumbersome names; you call the wrapper macros above + * instead. + * + * A few functions whose wrapper macros are defined above are actually + * declared in other headers, so as to guarantee that the + * declaration(s) of their other parameter type(s) are in scope. + */ +void BinarySink_put_data(BinarySink *, const void *data, size_t len); +void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte); +void BinarySink_put_byte(BinarySink *, unsigned char); +void BinarySink_put_bool(BinarySink *, bool); +void BinarySink_put_uint16(BinarySink *, unsigned long); +void BinarySink_put_uint32(BinarySink *, unsigned long); +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_asciz(BinarySink *, const char *str); +bool BinarySink_put_pstring(BinarySink *, const char *str); + +/* ---------------------------------------------------------------------- */ + +/* + * A complementary trait structure for _un_-marshalling. + * + * This structure contains client-visible data fields rather than + * methods, because that seemed more useful than leaving it totally + * opaque. But it's still got the self-pointer system that will allow + * the set of get_* macros to target one of these itself or any other + * type that 'derives' from it. So, for example, an SSH packet + * structure can act as a BinarySource while also having additional + * fields like the packet type. + */ +typedef enum BinarySourceError { + BSE_NO_ERROR, + BSE_OUT_OF_DATA, + BSE_INVALID +} BinarySourceError; +struct BinarySource { + /* + * (data, len) is the data block being decoded. pos is the current + * position within the block. + */ + const void *data; + size_t pos, len; + + /* + * 'err' indicates whether a decoding error has happened at any + * point. Once this has been set to something other than + * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling + * function. So you can safely do a long sequence of get_foo() + * operations and then test err just once at the end, rather than + * having to conditionalise every single get. + * + * The unmarshalling functions should always return some value, + * even if a decoding error occurs. Generally on error they'll + * return zero (if numeric) or the empty string (if string-based), + * or some other appropriate default value for more complicated + * types. + * + * If the usual return value is dynamically allocated (e.g. a + * Bignum, or a normal C 'char *' string), then the error value is + * also dynamic in the same way. So you have to free exactly the + * same set of things whether or not there was a decoding error, + * which simplifies exit paths - for example, you could call a big + * pile of get_foo functions, then put the actual handling of the + * results under 'if (!get_err(src))', and then free everything + * outside that if. + */ + BinarySourceError err; + + /* + * Self-pointer for the implicit derivation trick, same as + * BinarySink above. + */ + BinarySource *binarysource_; +}; + +/* + * Implementation macros, similar to BinarySink. + */ +#define BinarySource_IMPLEMENTATION BinarySource binarysource_[1] +#define BinarySource_INIT__(obj, data_, len_) \ + ((obj)->data = (data_), \ + (obj)->len = (len_), \ + (obj)->pos = 0, \ + (obj)->err = BSE_NO_ERROR, \ + (obj)->binarysource_ = (obj)) +#define BinarySource_BARE_INIT(obj, data_, len_) \ + TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0, \ + BinarySource_INIT__(obj, data_, len_)) +#define BinarySource_INIT(obj, data_, len_) \ + TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0, \ + BinarySource_INIT__(BinarySource_UPCAST(obj), data_, len_)) +#define BinarySource_DOWNCAST(object, type) \ + TYPECHECK((object) == ((type *)0)->binarysource_, \ + ((type *)(((char *)(object)) - offsetof(type, binarysource_)))) +#define BinarySource_UPCAST(object) \ + TYPECHECK((object)->binarysource_ == (BinarySource *)0, \ + (object)->binarysource_) +#define BinarySource_COPIED(obj) \ + ((obj)->binarysource_->binarysource_ = (obj)->binarysource_) + +#define get_data(src, len) \ + BinarySource_get_data(BinarySource_UPCAST(src), len) +#define get_byte(src) \ + BinarySource_get_byte(BinarySource_UPCAST(src)) +#define get_bool(src) \ + BinarySource_get_bool(BinarySource_UPCAST(src)) +#define get_uint16(src) \ + BinarySource_get_uint16(BinarySource_UPCAST(src)) +#define get_uint32(src) \ + BinarySource_get_uint32(BinarySource_UPCAST(src)) +#define get_uint64(src) \ + BinarySource_get_uint64(BinarySource_UPCAST(src)) +#define get_string(src) \ + BinarySource_get_string(BinarySource_UPCAST(src)) +#define get_asciz(src) \ + BinarySource_get_asciz(BinarySource_UPCAST(src)) +#define get_pstring(src) \ + BinarySource_get_pstring(BinarySource_UPCAST(src)) +#define get_mp_ssh1(src) \ + BinarySource_get_mp_ssh1(BinarySource_UPCAST(src)) +#define get_mp_ssh2(src) \ + BinarySource_get_mp_ssh2(BinarySource_UPCAST(src)) +#define get_rsa_ssh1_pub(src, rsa, order) \ + BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order) +#define get_rsa_ssh1_priv(src, rsa) \ + BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa) + +#define get_err(src) (BinarySource_UPCAST(src)->err) +#define get_avail(src) (BinarySource_UPCAST(src)->len - \ + BinarySource_UPCAST(src)->pos) +#define get_ptr(src) \ + ((const void *)( \ + (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ + BinarySource_UPCAST(src)->pos)) + +ptrlen BinarySource_get_data(BinarySource *, size_t); +unsigned char BinarySource_get_byte(BinarySource *); +bool BinarySource_get_bool(BinarySource *); +unsigned BinarySource_get_uint16(BinarySource *); +unsigned long BinarySource_get_uint32(BinarySource *); +uint64_t BinarySource_get_uint64(BinarySource *); +ptrlen BinarySource_get_string(BinarySource *); +const char *BinarySource_get_asciz(BinarySource *); +ptrlen BinarySource_get_pstring(BinarySource *); + +#endif /* PUTTY_MARSHAL_H */ diff --git a/minibidi.c b/minibidi.c index 6c062116..6838b440 100644 --- a/minibidi.c +++ b/minibidi.c @@ -3,7 +3,7 @@ * ------------ * Description: * ------------ - * This is an implemention of Unicode's Bidirectional Algorithm + * This is an implementation of Unicode's Bidirectional Algorithm * (known as UAX #9). * * http://www.unicode.org/reports/tr9/ @@ -55,14 +55,15 @@ typedef struct bidi_char { } bidi_char; /* function declarations */ -void flipThisRun(bidi_char *from, unsigned char* level, int max, int count); -int findIndexOfRun(unsigned char* level , int start, int count, int tlevel); -unsigned char getType(int ch); -unsigned char setOverrideBits(unsigned char level, unsigned char override); -int getPreviousLevel(unsigned char* level, int from); -int do_shape(bidi_char *line, bidi_char *to, int count); -int do_bidi(bidi_char *line, int count); -void doMirror(unsigned int *ch); +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 { @@ -89,7 +90,7 @@ enum { /* Shaping Types */ enum { - SL, /* Left-Joining, doesnt exist in U+0600 - U+06FF */ + 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 */ @@ -297,7 +298,8 @@ const shape_node shapetypes[] = { * max: the maximum level found in this line (should be unsigned char) * count: line size in bidi_char */ -void flipThisRun(bidi_char *from, unsigned char *level, int max, int count) +static void flipThisRun( + bidi_char *from, unsigned char *level, int max, int count) { int i, j, k, tlevel; bidi_char temp; @@ -323,7 +325,8 @@ void flipThisRun(bidi_char *from, unsigned char *level, int max, int count) /* * Finds the index of a run with level equals tlevel */ -int findIndexOfRun(unsigned char* level , int start, int count, int tlevel) +static int findIndexOfRun( + unsigned char *level , int start, int count, int tlevel) { int i; for (i=start; i 0) { unsigned char current = level[--from]; @@ -1086,9 +1090,10 @@ int getPreviousLevel(unsigned char* level, int from) */ int do_shape(bidi_char *line, bidi_char *to, int count) { - int i, tempShape, ligFlag; + int i, tempShape; + bool ligFlag = false; - for (ligFlag=i=0; i 0) switch (line[i-1].wc) { case 0x622: - ligFlag = 1; + ligFlag = true; if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) to[i].wc = 0xFEF6; else to[i].wc = 0xFEF5; break; case 0x623: - ligFlag = 1; + ligFlag = true; if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) to[i].wc = 0xFEF8; else to[i].wc = 0xFEF7; break; case 0x625: - ligFlag = 1; + ligFlag = true; if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) to[i].wc = 0xFEFA; else to[i].wc = 0xFEF9; break; case 0x627: - ligFlag = 1; + ligFlag = true; if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) to[i].wc = 0xFEFC; else @@ -1143,7 +1148,7 @@ int do_shape(bidi_char *line, bidi_char *to, int count) } if (ligFlag) { to[i-1].wc = 0x20; - ligFlag = 0; + ligFlag = false; break; } } @@ -1186,18 +1191,19 @@ int do_bidi(bidi_char *line, int count) unsigned char currentEmbedding; unsigned char currentOverride; unsigned char tempType; - int i, j, yes, bover; + int i, j; + bool yes, bover; /* Check the presence of R or AL types as optimization */ - yes = 0; + yes = false; for (i=0; ivt->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 = 0; - p->frontend = frontend; p->data = NULL; - p->to_server = TRUE; /* to be on the safe side */ + p->to_server = true; /* to be on the safe side */ p->name = p->instruction = NULL; - p->name_reqd = p->instr_reqd = FALSE; + p->name_reqd = p->instr_reqd = false; return p; } -void add_prompt(prompts_t *p, char *promptstr, int echo) +void add_prompt(prompts_t *p, char *promptstr, bool echo) { prompt_t *pr = snew(prompt_t); pr->prompt = promptstr; @@ -336,27 +349,14 @@ void burnstr(char *string) /* sfree(str), only clear it first */ } } -int toint(unsigned u) +int string_length_for_printf(size_t s) { - /* - * Convert an unsigned to an int, without running into the - * undefined behaviour which happens by the strict C standard if - * the value overflows. You'd hope that sensible compilers would - * do the sensible thing in response to a cast, but actually I - * don't trust modern compilers not to do silly things like - * assuming that _obviously_ you wouldn't have caused an overflow - * and so they can elide an 'if (i < 0)' test immediately after - * the cast. - * - * Sensible compilers ought of course to optimise this entire - * function into 'just return the input value'! - */ - if (u <= (unsigned)INT_MAX) - return (int)u; - else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ - return INT_MIN + (int)(u - (unsigned)INT_MIN); - else - return INT_MIN; /* fallback; should never occur on binary machines */ + /* 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; } /* @@ -465,47 +465,90 @@ char *dupprintf(const char *fmt, ...) return ret; } -struct strbuf { - char *s; - int len, size; +struct strbuf_impl { + int size; + struct strbuf visible; }; + +#define STRBUF_SET_PTR(buf, ptr) \ + ((buf)->visible.s = (ptr), \ + (buf)->visible.u = (unsigned char *)(buf)->visible.s) + +void *strbuf_append(strbuf *buf_o, size_t len) +{ + struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); + char *toret; + if (buf->size < buf->visible.len + len + 1) { + buf->size = (buf->visible.len + len + 1) * 5 / 4 + 512; + STRBUF_SET_PTR(buf, sresize(buf->visible.s, buf->size, char)); + } + toret = buf->visible.s + buf->visible.len; + buf->visible.len += len; + buf->visible.s[buf->visible.len] = '\0'; + return toret; +} + +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); +} + strbuf *strbuf_new(void) { - strbuf *buf = snew(strbuf); - buf->len = 0; + struct strbuf_impl *buf = snew(struct strbuf_impl); + BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); + buf->visible.len = 0; buf->size = 512; - buf->s = snewn(buf->size, char); - *buf->s = '\0'; - return buf; + STRBUF_SET_PTR(buf, snewn(buf->size, char)); + *buf->visible.s = '\0'; + return &buf->visible; } -void strbuf_free(strbuf *buf) +void strbuf_free(strbuf *buf_o) { - sfree(buf->s); + 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_str(strbuf *buf) +char *strbuf_to_str(strbuf *buf_o) { - return buf->s; -} -char *strbuf_to_str(strbuf *buf) -{ - char *ret = buf->s; + 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, const char *fmt, va_list ap) +void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) { - buf->s = dupvprintf_inner(buf->s, buf->len, &buf->size, fmt, ap); - buf->len += strlen(buf->s + buf->len); + 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, const char *fmt, ...) +void strbuf_catf(strbuf *buf_o, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - strbuf_catfv(buf, fmt, ap); + strbuf_catfv(buf_o, fmt, ap); va_end(ap); } +strbuf *strbuf_new_for_agent_query(void) +{ + strbuf *buf = strbuf_new(); + put_uint32(buf, 0); /* reserve space for length field */ + 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). @@ -647,6 +690,7 @@ void bufchain_init(bufchain *ch) { ch->head = ch->tail = NULL; ch->buffersize = 0; + ch->ic = NULL; } void bufchain_clear(bufchain *ch) @@ -698,6 +742,9 @@ void bufchain_add(bufchain *ch, const void *data, int len) ch->tail = newbuf; } } + + if (ch->ic) + queue_idempotent_callback(ch->ic); } void bufchain_consume(bufchain *ch, int len) @@ -750,6 +797,49 @@ void bufchain_fetch(bufchain *ch, void *data, int len) } } +void bufchain_fetch_consume(bufchain *ch, void *data, int len) +{ + bufchain_fetch(ch, data, len); + bufchain_consume(ch, len); +} + +bool bufchain_try_fetch_consume(bufchain *ch, void *data, int len) +{ + if (ch->buffersize >= len) { + bufchain_fetch_consume(ch, data, len); + return true; + } else { + return false; + } +} + +/* ---------------------------------------------------------------------- + * Sanitise terminal output that we have reason not to trust, e.g. + * because it appears in the login banner or password prompt from a + * server, which we'd rather not permit to use arbitrary escape + * sequences. + */ + +void sanitise_term_data(bufchain *out, const void *vdata, int len) +{ + const char *data = (const char *)vdata; + int i; + + /* + * FIXME: this method of sanitisation is ASCII-centric. It would + * be nice to permit SSH banners and the like to contain printable + * Unicode, but that would need a lot more complicated code here + * (not to mention knowing what character set it should interpret + * the data as). + */ + for (i = 0; i < len; i++) { + if (data[i] == '\n') + bufchain_add(out, "\r\n", 2); + else if (data[i] >= ' ' && data[i] < 0x7F) + bufchain_add(out, data + i, 1); + } +} + /* ---------------------------------------------------------------------- * My own versions of malloc, realloc and free. Because I want * malloc and realloc to bomb out and exit the program if they run @@ -901,7 +991,7 @@ void debug_printf(const char *fmt, ...) } -void debug_memdump(const void *buf, int len, int L) +void debug_memdump(const void *buf, int len, bool L) { int i; const unsigned char *p = buf; @@ -942,7 +1032,7 @@ void debug_memdump(const void *buf, int len, int L) * Determine whether or not a Conf represents a session which can * sensibly be launched right now. */ -int conf_launchable(Conf *conf) +bool conf_launchable(Conf *conf) { if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) return conf_get_str(conf, CONF_serline)[0] != 0; @@ -1000,13 +1090,13 @@ void smemclr(void *b, size_t n) { /* * Validate a manual host key specification (either entered in the - * GUI, or via -hostkey). If valid, we return TRUE, and update 'key' + * 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. + * valid, we return false. */ -int validate_manual_hostkey(char *key) +bool validate_manual_hostkey(char *key) { char *p, *q, *r, *s; @@ -1041,7 +1131,7 @@ int validate_manual_hostkey(char *key) for (i = 0; i < 16*3 - 1; i++) key[i] = tolower(q[i]); key[16*3 - 1] = '\0'; - return TRUE; + return true; } not_fingerprint:; @@ -1087,15 +1177,15 @@ int validate_manual_hostkey(char *key) goto not_ssh2_blob; /* sorry */ strcpy(key, q); - return TRUE; + return true; } not_ssh2_blob:; } - return FALSE; + return false; } -int smemeq(const void *av, const void *bv, size_t len) +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; @@ -1111,45 +1201,55 @@ int smemeq(const void *av, const void *bv, size_t len) return (0x100 - val) >> 8; } -int match_ssh_id(int stringlen, const void *string, const char *id) +int nullstrcmp(const char *a, const char *b) { - int idlen = strlen(id); - return (idlen == stringlen && !memcmp(string, id, idlen)); + if (a == NULL && b == NULL) + return 0; + if (a == NULL) + return -1; + if (b == NULL) + return +1; + return strcmp(a, b); } -void *get_ssh_string(int *datalen, const void **data, int *stringlen) +bool ptrlen_eq_string(ptrlen pl, const char *str) { - void *ret; - unsigned int len; - - if (*datalen < 4) - return NULL; - len = GET_32BIT_MSB_FIRST((const unsigned char *)*data); - if (*datalen - 4 < len) - return NULL; - ret = (void *)((const char *)*data + 4); - *datalen -= len + 4; - *data = (const char *)*data + len + 4; - *stringlen = len; - return ret; + size_t len = strlen(str); + return (pl.len == len && !memcmp(pl.ptr, str, len)); } -int get_ssh_uint32(int *datalen, const void **data, unsigned *ret) +bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) { - if (*datalen < 4) - return FALSE; - *ret = GET_32BIT_MSB_FIRST((const unsigned char *)*data); - *datalen -= 4; - *data = (const char *)*data + 4; - return TRUE; + return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); } -int strstartswith(const char *s, const char *t) +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; +} + +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 !memcmp(s, t, strlen(t)); } -int strendswith(const char *s, const char *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); @@ -1158,32 +1258,45 @@ int strendswith(const char *s, const char *t) char *buildinfo(const char *newline) { strbuf *buf = strbuf_new(); - extern const char commitid[]; /* in commitid.c */ 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__); -#elif defined _MSC_VER - strbuf_catf(buf, "%sCompiler: Visual Studio", newline); +#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", newline); #if _MSC_VER == 1900 strbuf_catf(buf, " 2015 / MSVC++ 14.0"); +#elif _MSC_VER == 1912 + strbuf_catf(buf, " 2017 / MSVC++ 14.12"); #elif _MSC_VER == 1800 strbuf_catf(buf, " 2013 / MSVC++ 12.0"); #elif _MSC_VER == 1700 strbuf_catf(buf, " 2012 / MSVC++ 11.0"); #elif _MSC_VER == 1600 strbuf_catf(buf, " 2010 / MSVC++ 10.0"); -#elif _MSC_VER == 1500 +#elif _MSC_VER == 1500 strbuf_catf(buf, " 2008 / MSVC++ 9.0"); -#elif _MSC_VER == 1400 +#elif _MSC_VER == 1400 strbuf_catf(buf, " 2005 / MSVC++ 8.0"); -#elif _MSC_VER == 1310 +#elif _MSC_VER == 1310 strbuf_catf(buf, " 2003 / MSVC++ 7.1"); +#elif _MSC_VER == 1300 + strbuf_catf(buf, " 2003 / MSVC++ 7.0"); #else strbuf_catf(buf, ", unrecognised version"); #endif @@ -1201,6 +1314,9 @@ char *buildinfo(const char *newline) } #endif +#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 @@ -1230,3 +1346,40 @@ char *buildinfo(const char *newline) return strbuf_to_str(buf); } + +int nullseat_output( + Seat *seat, bool is_stderr, const void *data, int 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, char *key_fingerprint, + 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; } + +void sk_free_peer_info(SocketPeerInfo *pi) +{ + if (pi) { + sfree((char *)pi->addr_text); + sfree((char *)pi->log_text); + sfree(pi); + } +} diff --git a/misc.h b/misc.h index 32e7bc32..2302fbbc 100644 --- a/misc.h +++ b/misc.h @@ -5,21 +5,14 @@ #ifndef PUTTY_MISC_H #define PUTTY_MISC_H +#include "defs.h" #include "puttymem.h" +#include "marshal.h" #include /* for FILE * */ #include /* for va_list */ #include /* for struct tm */ - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -typedef struct Filename Filename; -typedef struct FontSpec FontSpec; +#include /* for INT_MAX/MIN */ unsigned long parse_blocksize(const char *bs); char ctrlparse(char *s, char **next); @@ -38,14 +31,24 @@ char *dupprintf(const char *fmt, ...) ; char *dupvprintf(const char *fmt, va_list ap); void burnstr(char *string); -typedef struct strbuf strbuf; + +struct strbuf { + char *s; + unsigned char *u; + int len; + BinarySink_IMPLEMENTATION; + /* (also there's a surrounding implementation struct in misc.c) */ +}; strbuf *strbuf_new(void); void strbuf_free(strbuf *buf); -char *strbuf_str(strbuf *buf); /* does not free buf */ +void *strbuf_append(strbuf *buf, size_t len); char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */ void strbuf_catf(strbuf *buf, const char *fmt, ...); void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); +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. * @@ -55,12 +58,34 @@ void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); 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); -int toint(unsigned); +static inline int toint(unsigned u) +{ + /* + * Convert an unsigned to an int, without running into the + * undefined behaviour which happens by the strict C standard if + * the value overflows. You'd hope that sensible compilers would + * do the sensible thing in response to a cast, but actually I + * don't trust modern compilers not to do silly things like + * assuming that _obviously_ you wouldn't have caused an overflow + * and so they can elide an 'if (i < 0)' test immediately after + * the cast. + * + * Sensible compilers ought of course to optimise this entire + * function into 'just return the input value', and since it's + * also declared inline, elide it completely in their output. + */ + if (u <= (unsigned)INT_MAX) + return (int)u; + else if (u >= (unsigned)INT_MIN) /* wrap in cast _to_ unsigned is OK */ + return INT_MIN + (int)(u - (unsigned)INT_MIN); + else + return INT_MIN; /* fallback; should never occur on binary machines */ +} char *fgetline(FILE *fp); char *chomp(char *str); -int strstartswith(const char *s, const char *t); -int strendswith(const char *s, const char *t); +bool strstartswith(const char *s, const char *t); +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); @@ -69,11 +94,8 @@ struct bufchain_granule; struct bufchain_tag { struct bufchain_granule *head, *tail; int buffersize; /* current amount of buffered data */ + IdempotentCallback *ic; }; -#ifndef BUFCHAIN_TYPEDEF -typedef struct bufchain_tag bufchain; /* rest of declaration in misc.c */ -#define BUFCHAIN_TYPEDEF -#endif void bufchain_init(bufchain *ch); void bufchain_clear(bufchain *ch); @@ -82,11 +104,54 @@ void bufchain_add(bufchain *ch, const void *data, int len); void bufchain_prefix(bufchain *ch, void **data, int *len); void bufchain_consume(bufchain *ch, int len); void bufchain_fetch(bufchain *ch, void *data, int len); +void bufchain_fetch_consume(bufchain *ch, void *data, int len); +bool bufchain_try_fetch_consume(bufchain *ch, void *data, int len); -int validate_manual_hostkey(char *key); +void sanitise_term_data(bufchain *out, const void *vdata, int len); + +bool validate_manual_hostkey(char *key); struct tm ltime(void); +/* + * Special form of strcmp which can cope with NULL inputs. NULL is + * defined to sort before even the empty string. + */ +int nullstrcmp(const char *a, const char *b); + +static inline ptrlen make_ptrlen(const void *ptr, size_t len) +{ + ptrlen pl; + pl.ptr = ptr; + pl.len = len; + return pl; +} + +static inline ptrlen ptrlen_from_asciz(const char *str) +{ + return make_ptrlen(str, strlen(str)); +} + +static inline ptrlen ptrlen_from_strbuf(strbuf *sb) +{ + return make_ptrlen(sb->u, sb->len); +} + +bool ptrlen_eq_string(ptrlen pl, const char *str); +bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2); +bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); +char *mkstr(ptrlen pl); +int string_length_for_printf(size_t); +/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ +#define PTRLEN_PRINTF(pl) \ + string_length_for_printf((pl).len), (const char *)(pl).ptr +/* Make a ptrlen out of a compile-time string literal. We try to + * enforce that it _is_ a string literal by token-pasting "" on to it, + * which should provoke a compile error if it's any other kind of + * string. */ +#define PTRLEN_LITERAL(stringlit) \ + TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1)) + /* 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 @@ -97,26 +162,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 0 for mismatch or 1 for equality (unlike memcmp), hinted at - * by the 'eq' in the name. */ -int smemeq(const void *av, const void *bv, size_t len); - -/* Extracts an SSH-marshalled string from the start of *data. If - * successful (*datalen is not too small), advances data/datalen past - * the string and returns a pointer to the string itself and its - * length in *stringlen. Otherwise does nothing and returns NULL. - * - * Like strchr, this function can discard const from its parameter. - * Treat it as if it was a family of two functions, one returning a - * non-const string given a non-const pointer, and one taking and - * returning const. */ -void *get_ssh_string(int *datalen, const void **data, int *stringlen); -/* Extracts an SSH uint32, similarly. Returns TRUE on success, and - * leaves the extracted value in *ret. */ -int get_ssh_uint32(int *datalen, const void **data, unsigned *ret); -/* Given a not-necessarily-zero-terminated string in (length,data) - * form, check if it equals an ordinary C zero-terminated string. */ -int match_ssh_id(int stringlen, const void *string, const char *id); + * 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); char *buildinfo(const char *newline); @@ -135,10 +183,10 @@ char *buildinfo(const char *newline); #ifdef DEBUG void debug_printf(const char *fmt, ...); -void debug_memdump(const void *buf, int len, int L); +void debug_memdump(const void *buf, int len, bool L); #define debug(x) (debug_printf x) -#define dmemdump(buf,len) debug_memdump (buf, len, 0); -#define dmemdumpl(buf,len) debug_memdump (buf, len, 1); +#define dmemdump(buf,len) debug_memdump (buf, len, false); +#define dmemdumpl(buf,len) debug_memdump (buf, len, true); #else #define debug(x) #define dmemdump(buf,len) @@ -156,11 +204,31 @@ void debug_memdump(const void *buf, int len, int L); #define max(x,y) ( (x) > (y) ? (x) : (y) ) #endif +#define GET_64BIT_LSB_FIRST(cp) \ + (((uint64_t)(unsigned char)(cp)[0]) | \ + ((uint64_t)(unsigned char)(cp)[1] << 8) | \ + ((uint64_t)(unsigned char)(cp)[2] << 16) | \ + ((uint64_t)(unsigned char)(cp)[3] << 24) | \ + ((uint64_t)(unsigned char)(cp)[4] << 32) | \ + ((uint64_t)(unsigned char)(cp)[5] << 40) | \ + ((uint64_t)(unsigned char)(cp)[6] << 48) | \ + ((uint64_t)(unsigned char)(cp)[7] << 56)) + +#define PUT_64BIT_LSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)(value), \ + (cp)[1] = (unsigned char)((value) >> 8), \ + (cp)[2] = (unsigned char)((value) >> 16), \ + (cp)[3] = (unsigned char)((value) >> 24), \ + (cp)[4] = (unsigned char)((value) >> 32), \ + (cp)[5] = (unsigned char)((value) >> 40), \ + (cp)[6] = (unsigned char)((value) >> 48), \ + (cp)[7] = (unsigned char)((value) >> 56) ) + #define GET_32BIT_LSB_FIRST(cp) \ - (((unsigned long)(unsigned char)(cp)[0]) | \ - ((unsigned long)(unsigned char)(cp)[1] << 8) | \ - ((unsigned long)(unsigned char)(cp)[2] << 16) | \ - ((unsigned long)(unsigned char)(cp)[3] << 24)) + (((uint32_t)(unsigned char)(cp)[0]) | \ + ((uint32_t)(unsigned char)(cp)[1] << 8) | \ + ((uint32_t)(unsigned char)(cp)[2] << 16) | \ + ((uint32_t)(unsigned char)(cp)[3] << 24)) #define PUT_32BIT_LSB_FIRST(cp, value) ( \ (cp)[0] = (unsigned char)(value), \ @@ -177,10 +245,10 @@ void debug_memdump(const void *buf, int len, int L); (cp)[1] = (unsigned char)((value) >> 8) ) #define GET_32BIT_MSB_FIRST(cp) \ - (((unsigned long)(unsigned char)(cp)[0] << 24) | \ - ((unsigned long)(unsigned char)(cp)[1] << 16) | \ - ((unsigned long)(unsigned char)(cp)[2] << 8) | \ - ((unsigned long)(unsigned char)(cp)[3])) + (((uint32_t)(unsigned char)(cp)[0] << 24) | \ + ((uint32_t)(unsigned char)(cp)[1] << 16) | \ + ((uint32_t)(unsigned char)(cp)[2] << 8) | \ + ((uint32_t)(unsigned char)(cp)[3])) #define GET_32BIT(cp) GET_32BIT_MSB_FIRST(cp) @@ -192,6 +260,26 @@ void debug_memdump(const void *buf, int len, int L); #define PUT_32BIT(cp, value) PUT_32BIT_MSB_FIRST(cp, value) +#define GET_64BIT_MSB_FIRST(cp) \ + (((uint64_t)(unsigned char)(cp)[0] << 56) | \ + ((uint64_t)(unsigned char)(cp)[1] << 48) | \ + ((uint64_t)(unsigned char)(cp)[2] << 40) | \ + ((uint64_t)(unsigned char)(cp)[3] << 32) | \ + ((uint64_t)(unsigned char)(cp)[4] << 24) | \ + ((uint64_t)(unsigned char)(cp)[5] << 16) | \ + ((uint64_t)(unsigned char)(cp)[6] << 8) | \ + ((uint64_t)(unsigned char)(cp)[7])) + +#define PUT_64BIT_MSB_FIRST(cp, value) ( \ + (cp)[0] = (unsigned char)((value) >> 56), \ + (cp)[1] = (unsigned char)((value) >> 48), \ + (cp)[2] = (unsigned char)((value) >> 40), \ + (cp)[3] = (unsigned char)((value) >> 32), \ + (cp)[4] = (unsigned char)((value) >> 24), \ + (cp)[5] = (unsigned char)((value) >> 16), \ + (cp)[6] = (unsigned char)((value) >> 8), \ + (cp)[7] = (unsigned char)(value) ) + #define GET_16BIT_MSB_FIRST(cp) \ (((unsigned long)(unsigned char)(cp)[0] << 8) | \ ((unsigned long)(unsigned char)(cp)[1])) diff --git a/mkfiles.pl b/mkfiles.pl index ae15ac48..8e23b640 100755 --- a/mkfiles.pl +++ b/mkfiles.pl @@ -122,12 +122,12 @@ $listref = $lastlistref; $prog = undef; die "$.: unexpected + line\n" if !defined $lastlistref; - } elsif ($_[1] eq "=") { + } elsif ($#_ >= 1 && $_[1] eq "=") { $groups{$_[0]} = [] if !defined $groups{$_[0]}; $listref = $groups{$_[0]}; $prog = undef; shift @objs; # eat the group name - } elsif ($_[1] eq ":") { + } elsif ($#_ >= 1 && $_[1] eq ":") { $listref = []; $prog = $_[0]; shift @objs; # eat the program name @@ -268,7 +268,7 @@ ($) # Returns true if the argument is a known makefile type. Otherwise, # prints a warning and returns false; if (grep { $type eq $_ } - ("vc","vcproj","cygwin","borland","lcc","devcppproj","gtk","unix", + ("vc","vcproj","cygwin","lcc","devcppproj","gtk","unix", "am","osx","vstudio10","vstudio12","clangcl")) { return 1; } @@ -364,7 +364,7 @@ sub splitline { $len = (defined $width ? $width : 76); $splitchar = (defined $splitchar ? $splitchar : '\\'); while (length $line > $len) { - $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,}?\s(.*)$/; + $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,})?\s(.*)$/; $result .= $1; $result .= " ${splitchar}\n\t\t" if $2 ne ''; $line = $2; @@ -448,14 +448,9 @@ sub manpages { # using the real Visual Studio header files and libraries. In # order to run it, you will need: # - # - MinGW windres on your PATH. - # * On Ubuntu as of 16.04, you can apt-get install - # binutils-mingw-w64-x86-64 and binutils-mingw-w64-i686 - # which will provide (respectively) 64- and 32-bit versions, - # under the names to which RCCMD is defined below. - # - clang-cl and lld-link on your PATH. + # - 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 2017-02-05. + # 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 @@ -492,37 +487,70 @@ sub manpages { # 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. open OUT, ">$makefiles{'clangcl'}"; select OUT; print "# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n". - "# and MinGW's windres, using GNU make on Linux.\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". - "RCCMD = x86_64-w64-mingw32-windres\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". - "RCCMD = i686-w64-mingw32-windres\n". + "PLATFORMCFLAGS =\n". "endif\n". "CC = \$(CCCMD) --target=\$(CCTARGET)\n". - &splitline("RC = \$(RCCMD) --preprocessor=\$(CCCMD) ". - "--preprocessor-arg=/TC --preprocessor-arg=/E")."\n". + "RC = \$(RCCMD) /c 1252 \n". + "RCPREPROC = \$(CCCMD) /P /TC\n". "LD = lld-link\n". "\n". "# C compilation flags\n". &splitline("CFLAGS = /nologo /W3 /O1 " . (join " ", map {"-I$dirpfx$_"} @srcdirs) . " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 ". - "/D_CRT_SECURE_NO_WARNINGS")."\n". + "/D_CRT_SECURE_NO_WARNINGS /D_WINSOCK_DEPRECATED_NO_WARNINGS"). + " \$(PLATFORMCFLAGS)\n". "LFLAGS = /incremental:no /dynamicbase /nxcompat\n". - &splitline("RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs). - " -DWIN32 -D_WIN32 -DWINVER=0x0400")."\n". + &splitline("RCPPFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs). + " -DWIN32 -D_WIN32 -DWINVER=0x0400")." \$(RCFL)\n". "\n". &def($makefile_extra{'clangcl'}->{'vars'}) . "\n". @@ -531,31 +559,44 @@ sub manpages { print "\n\n"; foreach $p (&prognames("G:C")) { ($prog, $type) = split ",", $p; - $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res.o", undef); + $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.o", "X.lib"); + $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) $objstr")."\n\n"; + "/subsystem:$subsys\$(SUBSYSVER) ". + "\$(EXTRA_$subsys) $objstr")."\n\n"; } - foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res.o", $dirpfx, "/", "vc")) { + my $rc_pp_rules = ""; + 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\.o$/) { - print "\t\$(RC) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n"; + my $rule; + my @deps = @{$d->{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 .= "$rcpp: $rc\n" . + "\t\$(RCPREPROC) \$(RCPPFLAGS) /Fi\$\@ \$<\n\n"; + $deps[0] = $rcpp; } else { - print "\t\$(CC) /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<\n\n"; + $rule = "\$(CC) /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<"; } + print &splitline(sprintf("%s: %s", $d->{obj}, + join " ", @$extradeps, @deps)), "\n"; + print "\t" . $rule . "\n\n"; } - print "\n"; + print "\n" . $rc_pp_rules; print &def($makefile_extra{'clangcl'}->{'end'}); print "\nclean:\n". &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ". - "\$(BUILDDIR)*.res.o \$(BUILDDIR)*.map ". + "\$(BUILDDIR)*.res \$(BUILDDIR)*.map ". "\$(BUILDDIR)*.exe.manifest")."\n"; select STDOUT; close OUT; } @@ -633,121 +674,6 @@ sub manpages { } -##-- Borland makefile -if (defined $makefiles{'borland'}) { - $dirpfx = &dirpfx($makefiles{'borland'}, "\\"); - - %stdlibs = ( # Borland provides many Win32 API libraries intrinsically - "advapi32" => 1, - "comctl32" => 1, - "comdlg32" => 1, - "gdi32" => 1, - "imm32" => 1, - "shell32" => 1, - "user32" => 1, - "winmm" => 1, - "winspool" => 1, - "wsock32" => 1, - ); - open OUT, ">$makefiles{'borland'}"; select OUT; - print - "# Makefile for $project_name under Borland C.\n". - "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n". - "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n"; - # bcc32 command line option is -D not /D - ($_ = $help) =~ s/([=" ])\/D/$1-D/gs; - print $_; - print - "\n". - "# If you rename this file to `Makefile', you should change this line,\n". - "# so that the .rsp files still depend on the correct makefile.\n". - "MAKEFILE = Makefile.bor\n". - "\n". - "# C compilation flags\n". - "CFLAGS = -D_WINDOWS -DWINVER=0x0500\n". - "# Resource compilation flags\n". - "RCFLAGS = -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401\n". - "\n". - "# Get include directory for resource compiler\n". - "!if !\$d(BCB)\n". - "BCB = \$(MAKEDIR)\\..\n". - "!endif\n". - "\n". - &def($makefile_extra{'borland'}->{'vars'}) . - "\n". - ".c.obj:\n". - &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)". - " \$(CFLAGS) \$(XFLAGS) ". - (join " ", map {"-I$dirpfx$_"} @srcdirs) . - " /c \$*.c",69)."\n". - ".rc.res:\n". - &splitline("\tbrcc32 \$(RCFL) -i \$(BCB)\\include -r". - " \$(RCFLAGS) \$*.rc",69)."\n". - "\n"; - print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C")); - print "\n\n"; - foreach $p (&prognames("G:C")) { - ($prog, $type) = split ",", $p; - $objstr = &objects($p, "X.obj", "X.res", undef); - print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n"; - my $ap = ($type eq "G") ? "-aa" : "-ap"; - print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n"; - } - foreach $p (&prognames("G:C")) { - ($prog, $type) = split ",", $p; - print $prog, ".rsp: \$(MAKEFILE)\n"; - $objstr = &objects($p, "X.obj", undef, undef); - @objlist = split " ", $objstr; - @objlines = (""); - foreach $i (@objlist) { - if (length($objlines[$#objlines] . " $i") > 50) { - push @objlines, ""; - } - $objlines[$#objlines] .= " $i"; - } - $c0w = ($type eq "G") ? "c0w32" : "c0x32"; - print "\techo $c0w + > $prog.rsp\n"; - for ($i=0; $i<=$#objlines; $i++) { - $plus = ($i < $#objlines ? " +" : ""); - print "\techo$objlines[$i]$plus >> $prog.rsp\n"; - } - print "\techo $prog.exe >> $prog.rsp\n"; - $objstr = &objects($p, "X.obj", "X.res", undef); - @libs = split " ", &objects($p, undef, undef, "X"); - @libs = grep { !$stdlibs{$_} } @libs; - unshift @libs, "cw32", "import32"; - $libstr = join ' ', @libs; - print "\techo nul,$libstr, >> $prog.rsp\n"; - print "\techo " . &objects($p, undef, "X.res", undef) . " >> $prog.rsp\n"; - print "\n"; - } - foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "borland")) { - if ($forceobj{$d->{obj_orig}}) { - printf("%s: FORCE\n", $d->{obj}); - } else { - print &splitline(sprintf("%s: %s", $d->{obj}, - join " ", @{$d->{deps}})), "\n"; - } - } - print "\n"; - print &def($makefile_extra{'borland'}->{'end'}); - print "\nclean:\n". - "\t-del *.obj\n". - "\t-del *.exe\n". - "\t-del *.res\n". - "\t-del *.pch\n". - "\t-del *.aps\n". - "\t-del *.il*\n". - "\t-del *.pdb\n". - "\t-del *.rsp\n". - "\t-del *.tds\n". - "\t-del *.\$\$\$\$\$\$\n". - "\n". - "FORCE:\n". - "\t-rem dummy command\n"; - select STDOUT; close OUT; -} - if (defined $makefiles{'vc'}) { $dirpfx = &dirpfx($makefiles{'vc'}, "\\"); @@ -767,7 +693,7 @@ sub manpages { "# 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\n". + " /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". @@ -1966,7 +1892,7 @@ sub manpages { "# ** DO NOT EDIT **\r\n". "\r\n". # No difference between DEBUG and RELEASE here as in 'vcproj', because - # Dev-C++ does not support mutiple compilation profiles in one single project. + # 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". diff --git a/network.h b/network.h index d58635b6..a394a356 100644 --- a/network.h +++ b/network.h @@ -13,39 +13,40 @@ #ifndef PUTTY_NETWORK_H #define PUTTY_NETWORK_H -#ifndef DONE_TYPEDEFS -#define DONE_TYPEDEFS -typedef struct conf_tag Conf; -typedef struct backend_tag Backend; -typedef struct terminal_tag Terminal; -#endif +#include "defs.h" + +typedef struct SocketVtable SocketVtable; +typedef struct PlugVtable PlugVtable; -typedef struct SockAddr_tag *SockAddr; -/* pay attention to levels of indirection */ -typedef struct socket_function_table **Socket; -typedef struct plug_function_table **Plug; +struct Socket { + const struct SocketVtable *vt; +}; -struct socket_function_table { - Plug(*plug) (Socket s, Plug p); +struct SocketVtable { + Plug *(*plug) (Socket *s, Plug *p); /* use a different plug (return the old one) */ /* if p is NULL, it doesn't change the plug */ /* but it does return the one it's using */ - void (*close) (Socket s); - int (*write) (Socket s, const char *data, int len); - int (*write_oob) (Socket s, const char *data, int len); - void (*write_eof) (Socket s); - void (*flush) (Socket s); - void (*set_frozen) (Socket s, int is_frozen); + void (*close) (Socket *s); + int (*write) (Socket *s, const void *data, int len); + int (*write_oob) (Socket *s, const void *data, int len); + void (*write_eof) (Socket *s); + void (*flush) (Socket *s); + void (*set_frozen) (Socket *s, bool is_frozen); /* ignored by tcp, but vital for ssl */ - const char *(*socket_error) (Socket s); - char *(*peer_info) (Socket s); + const char *(*socket_error) (Socket *s); + SocketPeerInfo *(*peer_info) (Socket *s); }; typedef union { void *p; int i; } accept_ctx_t; -typedef Socket (*accept_fn_t)(accept_ctx_t ctx, Plug plug); +typedef Socket *(*accept_fn_t)(accept_ctx_t ctx, Plug *plug); + +struct Plug { + const struct PlugVtable *vt; +}; -struct plug_function_table { - void (*log)(Plug p, int type, SockAddr addr, int port, +struct PlugVtable { + void (*log)(Plug *p, int type, SockAddr *addr, int port, const char *error_msg, int error_code); /* * Passes the client progress reports on the process of setting @@ -64,12 +65,12 @@ struct plug_function_table { * proxy command, so the receiver should probably prefix it to * indicate this. */ - int (*closing) - (Plug p, const char *error_msg, int error_code, int calling_back); + void (*closing) + (Plug *p, const char *error_msg, int error_code, bool calling_back); /* error_msg is NULL iff it is not an error (ie it closed normally) */ /* calling_back != 0 iff there is a Plug function */ /* currently running (would cure the fixme in try_send()) */ - int (*receive) (Plug p, int urgent, char *data, int len); + void (*receive) (Plug *p, int urgent, char *data, int len); /* * - urgent==0. `data' points to `len' bytes of perfectly * ordinary data. @@ -80,13 +81,13 @@ struct plug_function_table { * - urgent==2. `data' points to `len' bytes of data, * the first of which was the one at the Urgent mark. */ - void (*sent) (Plug p, int bufsize); + void (*sent) (Plug *p, int bufsize); /* * The `sent' function is called when the pending send backlog * on a socket is cleared or partially cleared. The new backlog * size is passed in the `bufsize' parameter. */ - int (*accepting)(Plug p, accept_fn_t constructor, accept_ctx_t ctx); + int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); /* * `accepting' is called only on listener-type sockets, and is * passed a constructor function+context that will create a fresh @@ -98,77 +99,80 @@ struct plug_function_table { /* proxy indirection layer */ /* NB, control of 'addr' is passed via new_connection, which takes * responsibility for freeing it */ -Socket new_connection(SockAddr addr, const char *hostname, - int port, int privport, - int oobinline, int nodelay, int keepalive, - Plug plug, Conf *conf); -Socket new_listener(const char *srcaddr, int port, Plug plug, - int local_host_only, Conf *conf, int addressfamily); -SockAddr name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, void *frontend_for_logging, - const char *lookup_reason_for_logging); -int proxy_for_destination (SockAddr addr, const char *hostname, int port, - Conf *conf); +Socket *new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf); +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, + Conf *conf, int addressfamily, LogContext *logctx, + const char *lookup_reason_for_logging); +bool proxy_for_destination (SockAddr *addr, const char *hostname, int port, + Conf *conf); /* platform-dependent callback from new_connection() */ /* (same caveat about addr as new_connection()) */ -Socket platform_new_connection(SockAddr addr, const char *hostname, - int port, int privport, - int oobinline, int nodelay, int keepalive, - Plug plug, Conf *conf); +Socket *platform_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 */ void sk_cleanup(void); /* called just before program exit */ -SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family); -SockAddr sk_nonamelookup(const char *host); -void sk_getaddr(SockAddr addr, char *buf, int buflen); -int sk_addr_needs_port(SockAddr addr); -int sk_hostname_is_local(const char *name); -int sk_address_is_local(SockAddr addr); -int sk_address_is_special_local(SockAddr addr); -int sk_addrtype(SockAddr addr); -void sk_addrcopy(SockAddr addr, char *buf); -void sk_addr_free(SockAddr addr); +SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family); +SockAddr *sk_nonamelookup(const char *host); +void sk_getaddr(SockAddr *addr, char *buf, int buflen); +bool sk_addr_needs_port(SockAddr *addr); +bool sk_hostname_is_local(const char *name); +bool sk_address_is_local(SockAddr *addr); +bool sk_address_is_special_local(SockAddr *addr); +int sk_addrtype(SockAddr *addr); +void sk_addrcopy(SockAddr *addr, char *buf); +void sk_addr_free(SockAddr *addr); /* sk_addr_dup generates another SockAddr which contains the same data * as the original one and can be freed independently. May not actually * physically _duplicate_ it: incrementing a reference count so that * one more free is required before it disappears is an acceptable * implementation. */ -SockAddr sk_addr_dup(SockAddr addr); +SockAddr *sk_addr_dup(SockAddr *addr); /* NB, control of 'addr' is passed via sk_new, which takes responsibility * for freeing it, as for new_connection() */ -Socket sk_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, int keepalive, Plug p); - -Socket sk_newlistener(const char *srcaddr, int port, Plug plug, - int local_host_only, int address_family); - -#define sk_plug(s,p) (((*s)->plug) (s, p)) -#define sk_close(s) (((*s)->close) (s)) -#define sk_write(s,buf,len) (((*s)->write) (s, buf, len)) -#define sk_write_oob(s,buf,len) (((*s)->write_oob) (s, buf, len)) -#define sk_write_eof(s) (((*s)->write_eof) (s)) -#define sk_flush(s) (((*s)->flush) (s)) - -#ifdef DEFINE_PLUG_METHOD_MACROS -#define plug_log(p,type,addr,port,msg,code) (((*p)->log) (p, type, addr, port, msg, code)) -#define plug_closing(p,msg,code,callback) (((*p)->closing) (p, msg, code, callback)) -#define plug_receive(p,urgent,buf,len) (((*p)->receive) (p, urgent, buf, len)) -#define plug_sent(p,bufsize) (((*p)->sent) (p, bufsize)) -#define plug_accepting(p, constructor, ctx) (((*p)->accepting)(p, constructor, ctx)) -#endif +Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, + bool nodelay, bool keepalive, Plug *p); + +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int address_family); + +#define sk_plug(s,p) (((s)->vt->plug) (s, p)) +#define sk_close(s) (((s)->vt->close) (s)) +#define sk_write(s,buf,len) (((s)->vt->write) (s, buf, len)) +#define sk_write_oob(s,buf,len) (((s)->vt->write_oob) (s, buf, len)) +#define sk_write_eof(s) (((s)->vt->write_eof) (s)) +#define sk_flush(s) (((s)->vt->flush) (s)) + +#define plug_log(p,type,addr,port,msg,code) \ + (((p)->vt->log) (p, type, addr, port, msg, code)) +#define plug_closing(p,msg,code,callback) \ + (((p)->vt->closing) (p, msg, code, callback)) +#define plug_receive(p,urgent,buf,len) \ + (((p)->vt->receive) (p, urgent, buf, len)) +#define plug_sent(p,bufsize) \ + (((p)->vt->sent) (p, bufsize)) +#define plug_accepting(p, constructor, ctx) \ + (((p)->vt->accepting)(p, constructor, ctx)) /* * Special error values are returned from sk_namelookup and sk_new * if there's a problem. These functions extract an error message, * or return NULL if there's no problem. */ -const char *sk_addr_error(SockAddr addr); -#define sk_socket_error(s) (((*s)->socket_error) (s)) +const char *sk_addr_error(SockAddr *addr); +#define sk_socket_error(s) (((s)->vt->socket_error) (s)) /* * Set the `frozen' flag on a socket. A frozen socket is one in @@ -187,14 +191,54 @@ const char *sk_addr_error(SockAddr addr); * associated local socket in order to avoid unbounded buffer * growth. */ -#define sk_set_frozen(s, is_frozen) (((*s)->set_frozen) (s, is_frozen)) +#define sk_set_frozen(s, is_frozen) (((s)->vt->set_frozen) (s, is_frozen)) /* - * Return a (dynamically allocated) string giving some information - * about the other end of the socket, suitable for putting in log - * files. May be NULL if nothing is available at all. + * Return a structure giving some information about the other end of + * the socket. May be NULL, if nothing is available at all. If it is + * not NULL, then it is dynamically allocated, and should be freed by + * a call to sk_free_peer_info(). See below for the definition. */ -#define sk_peer_info(s) (((*s)->peer_info) (s)) +#define sk_peer_info(s) (((s)->vt->peer_info) (s)) + +/* + * The structure returned from sk_peer_info, and a function to free + * one (in misc.c). + */ +struct SocketPeerInfo { + int addressfamily; + + /* + * Text form of the IPv4 or IPv6 address of the other end of the + * socket, if available, in the standard text representation. + */ + const char *addr_text; + + /* + * Binary form of the same address. Filled in if and only if + * addr_text is not NULL. You can tell which branch of the union + * is used by examining 'addressfamily'. + */ + union { + unsigned char ipv6[16]; + unsigned char ipv4[4]; + } addr_bin; + + /* + * Remote port number, or -1 if not available. + */ + int port; + + /* + * Free-form text suitable for putting in log messages. For IP + * sockets, repeats the address and port information from above. + * But it can be completely different, e.g. for Unix-domain + * sockets it gives information about the uid, gid and pid of the + * connecting process. + */ + const char *log_text; +}; +void sk_free_peer_info(SocketPeerInfo *pi); /* * Simple wrapper on getservbyname(), needed by ssh.c. Returns the @@ -215,7 +259,12 @@ char *get_hostname(void); * Trivial socket implementation which just stores an error. Found in * errsock.c. */ -Socket new_error_socket(const char *errmsg, Plug plug); +Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...); + +/* + * Trivial plug that does absolutely nothing. Found in nullplug.c. + */ +extern Plug *const nullplug; /* ---------------------------------------------------------------------- * Functions defined outside the network code, which have to be @@ -226,13 +275,10 @@ Socket new_error_socket(const char *errmsg, Plug plug); /* * Exports from be_misc.c. */ -void backend_socket_log(void *frontend, int type, SockAddr addr, int port, +void backend_socket_log(Seat *seat, LogContext *logctx, + int type, SockAddr *addr, int port, const char *error_msg, int error_code, Conf *conf, - int session_started); -#ifndef BUFCHAIN_TYPEDEF -typedef struct bufchain_tag bufchain; /* rest of declaration in misc.c */ -#define BUFCHAIN_TYPEDEF -#endif -void log_proxy_stderr(Plug plug, bufchain *buf, const void *vdata, int len); + bool session_started); +void log_proxy_stderr(Plug *plug, bufchain *buf, const void *vdata, int len); #endif diff --git a/nocmdline.c b/nocmdline.c new file mode 100644 index 00000000..090bfba7 --- /dev/null +++ b/nocmdline.c @@ -0,0 +1,42 @@ +/* + * 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) +{ + assert(false && "cmdline_process_param should never be called"); +} + +/* + * This variable will be referred to, so it has to exist. It's ignored. + */ +int cmdline_tooltype = 0; diff --git a/nocproxy.c b/nocproxy.c index d2aeb978..45e36a35 100644 --- a/nocproxy.c +++ b/nocproxy.c @@ -8,17 +8,16 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "putty.h" #include "network.h" #include "proxy.h" -void proxy_socks5_offerencryptedauth(char * command, int * len) +void proxy_socks5_offerencryptedauth(BinarySink *bs) { /* For telnet, don't add any new encrypted authentication routines */ } -int proxy_socks5_handlechap (Proxy_Socket p) +int proxy_socks5_handlechap (ProxySocket *p) { plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" @@ -27,7 +26,7 @@ int proxy_socks5_handlechap (Proxy_Socket p) return 1; } -int proxy_socks5_selectchap(Proxy_Socket p) +int proxy_socks5_selectchap(ProxySocket *p) { plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" " in telnet-only build", diff --git a/noshare.c b/noshare.c index 9a888454..bc6d0efc 100644 --- a/noshare.c +++ b/noshare.c @@ -13,7 +13,7 @@ #include "network.h" int platform_ssh_share(const char *name, Conf *conf, - Plug downplug, Plug upplug, Socket *sock, + Plug *downplug, Plug *upplug, Socket **sock, char **logtext, char **ds_err, char **us_err, int can_upstream, int can_downstream) { diff --git a/nullplug.c b/nullplug.c new file mode 100644 index 00000000..ca6c7c2c --- /dev/null +++ b/nullplug.c @@ -0,0 +1,42 @@ +/* + * 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" + +static void nullplug_socket_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) +{ +} + +static void nullplug_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ +} + +static void nullplug_receive(Plug *plug, int urgent, char *data, int len) +{ +} + +static void nullplug_sent(Plug *plug, int bufsize) +{ +} + +static const PlugVtable nullplug_plugvt = { + nullplug_socket_log, + nullplug_closing, + nullplug_receive, + nullplug_sent, + NULL +}; + +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/pageant.c b/pageant.c index 2d9a7402..fb17a077 100644 --- a/pageant.c +++ b/pageant.c @@ -11,8 +11,8 @@ #include "pageant.h" /* - * We need this to link with the RSA code, because rsaencrypt() - * pads its data with random bytes. Since we only use rsadecrypt() + * We need this to link with the RSA code, because rsa_ssh1_encrypt() + * pads its data with random bytes. Since we only use rsa_ssh1_decrypt() * and the signing functions, which are deterministic, this should * never be called. * @@ -27,23 +27,13 @@ int random_byte(void) return 0; /* unreachable, but placate optimiser */ } -static int pageant_local = FALSE; +static bool pageant_local = false; /* * rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys. */ static tree234 *rsakeys, *ssh2keys; -/* - * Blob structure for passing to the asymmetric SSH-2 key compare - * function, prototyped here. - */ -struct blob { - const unsigned char *blob; - int len; -}; -static int cmpkeys_ssh2_asymm(void *av, void *bv); - /* * Key comparison function for the 2-3-4 tree of RSA keys. */ @@ -84,179 +74,87 @@ static int cmpkeys_rsa(void *av, void *bv) return 0; } -/* - * Key comparison function for the 2-3-4 tree of SSH-2 keys. - */ -static int cmpkeys_ssh2(void *av, void *bv) -{ - struct ssh2_userkey *a = (struct ssh2_userkey *) av; - struct ssh2_userkey *b = (struct ssh2_userkey *) bv; - int i; - int alen, blen; - unsigned char *ablob, *bblob; - int c; - - /* - * Compare purely by public blob. - */ - ablob = a->alg->public_blob(a->data, &alen); - bblob = b->alg->public_blob(b->data, &blen); - - c = 0; - for (i = 0; i < alen && i < blen; i++) { - if (ablob[i] < bblob[i]) { - c = -1; - break; - } else if (ablob[i] > bblob[i]) { - c = +1; - break; - } - } - if (c == 0 && i < alen) - c = +1; /* a is longer */ - if (c == 0 && i < blen) - c = -1; /* a is longer */ - - sfree(ablob); - sfree(bblob); - - return c; -} - /* * Key comparison function for looking up a blob in the 2-3-4 tree * of SSH-2 keys. */ static int cmpkeys_ssh2_asymm(void *av, void *bv) { - struct blob *a = (struct blob *) av; + ptrlen *ablob = (ptrlen *) av; struct ssh2_userkey *b = (struct ssh2_userkey *) bv; - int i; - int alen, blen; - const unsigned char *ablob; - unsigned char *bblob; - int c; + strbuf *bblob; + int i, c; /* * Compare purely by public blob. */ - ablob = a->blob; - alen = a->len; - bblob = b->alg->public_blob(b->data, &blen); + bblob = strbuf_new(); + ssh_key_public_blob(b->key, BinarySink_UPCAST(bblob)); c = 0; - for (i = 0; i < alen && i < blen; i++) { - if (ablob[i] < bblob[i]) { + for (i = 0; i < ablob->len && i < bblob->len; i++) { + unsigned char abyte = ((unsigned char *)ablob->ptr)[i]; + if (abyte < bblob->u[i]) { c = -1; break; - } else if (ablob[i] > bblob[i]) { + } else if (abyte > bblob->u[i]) { c = +1; break; } } - if (c == 0 && i < alen) + if (c == 0 && i < ablob->len) c = +1; /* a is longer */ - if (c == 0 && i < blen) + if (c == 0 && i < bblob->len) c = -1; /* a is longer */ - sfree(bblob); + strbuf_free(bblob); return c; } /* - * Create an SSH-1 key list in a malloc'ed buffer; return its - * length. + * Main key comparison function for the 2-3-4 tree of SSH-2 keys. */ -void *pageant_make_keylist1(int *length) +static int cmpkeys_ssh2(void *av, void *bv) { - int i, nkeys, len; - struct RSAKey *key; - unsigned char *blob, *p, *ret; - int bloblen; - - /* - * Count up the number and length of keys we hold. - */ - len = 4; - nkeys = 0; - for (i = 0; NULL != (key = index234(rsakeys, i)); i++) { - nkeys++; - blob = rsa_public_blob(key, &bloblen); - len += bloblen; - sfree(blob); - len += 4 + strlen(key->comment); - } + struct ssh2_userkey *a = (struct ssh2_userkey *) av; + strbuf *ablob; + ptrlen apl; + int toret; + + ablob = strbuf_new(); + ssh_key_public_blob(a->key, BinarySink_UPCAST(ablob)); + apl.ptr = ablob->u; + apl.len = ablob->len; + toret = cmpkeys_ssh2_asymm(&apl, bv); + strbuf_free(ablob); + return toret; +} - /* Allocate the buffer. */ - p = ret = snewn(len, unsigned char); - if (length) *length = len; +void pageant_make_keylist1(BinarySink *bs) +{ + int i; + struct RSAKey *key; - PUT_32BIT(p, nkeys); - p += 4; + put_uint32(bs, count234(rsakeys)); for (i = 0; NULL != (key = index234(rsakeys, i)); i++) { - blob = rsa_public_blob(key, &bloblen); - memcpy(p, blob, bloblen); - p += bloblen; - sfree(blob); - PUT_32BIT(p, strlen(key->comment)); - memcpy(p + 4, key->comment, strlen(key->comment)); - p += 4 + strlen(key->comment); + rsa_ssh1_public_blob(bs, key, RSA_SSH1_EXPONENT_FIRST); + put_stringz(bs, key->comment); } - - assert(p - ret == len); - return ret; } -/* - * Create an SSH-2 key list in a malloc'ed buffer; return its - * length. - */ -void *pageant_make_keylist2(int *length) +void pageant_make_keylist2(BinarySink *bs) { + int i; struct ssh2_userkey *key; - int i, len, nkeys; - unsigned char *blob, *p, *ret; - int bloblen; - - /* - * Count up the number and length of keys we hold. - */ - len = 4; - nkeys = 0; - for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) { - nkeys++; - len += 4; /* length field */ - blob = key->alg->public_blob(key->data, &bloblen); - len += bloblen; - sfree(blob); - len += 4 + strlen(key->comment); - } - /* Allocate the buffer. */ - p = ret = snewn(len, unsigned char); - if (length) *length = len; - - /* - * Packet header is the obvious five bytes, plus four - * bytes for the key count. - */ - PUT_32BIT(p, nkeys); - p += 4; + put_uint32(bs, count234(ssh2keys)); for (i = 0; NULL != (key = index234(ssh2keys, i)); i++) { - blob = key->alg->public_blob(key->data, &bloblen); - PUT_32BIT(p, bloblen); - p += 4; - memcpy(p, blob, bloblen); - p += bloblen; - sfree(blob); - PUT_32BIT(p, strlen(key->comment)); - memcpy(p + 4, key->comment, strlen(key->comment)); - p += 4 + strlen(key->comment); + strbuf *blob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(blob)); + put_stringsb(bs, blob); + put_stringz(bs, key->comment); } - - assert(p - ret == len); - return ret; } static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...) @@ -283,25 +181,21 @@ static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...) } } -void *pageant_handle_msg(const void *msg, int msglen, int *outlen, - void *logctx, pageant_logfn_t logfn) +void pageant_handle_msg(BinarySink *bs, + const void *msgdata, int msglen, + void *logctx, pageant_logfn_t logfn) { - const unsigned char *p = msg; - const unsigned char *msgend; - unsigned char *ret = snewn(AGENT_MAX_MSGLEN, unsigned char); + BinarySource msg[1]; int type; - const char *fail_reason; - msgend = p + msglen; + BinarySource_BARE_INIT(msg, msgdata, msglen); - /* - * Get the message type. - */ - if (msgend < p+1) { - fail_reason = "message contained no type code"; - goto failure; + type = get_byte(msg); + if (get_err(msg)) { + pageant_failure_msg(bs, "message contained no type code", + logctx, logfn); + return; } - type = *p++; switch (type) { case SSH1_AGENTC_REQUEST_RSA_IDENTITIES: @@ -309,32 +203,21 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER. */ { - int len; - void *keylist; - plog(logctx, logfn, "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES"); - ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER; - keylist = pageant_make_keylist1(&len); - if (len + 5 > AGENT_MAX_MSGLEN) { - sfree(keylist); - fail_reason = "output would exceed max msglen"; - goto failure; - } - PUT_32BIT(ret, len + 1); - memcpy(ret + 5, keylist, len); + put_byte(bs, SSH1_AGENT_RSA_IDENTITIES_ANSWER); + pageant_make_keylist1(bs); plog(logctx, logfn, "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); if (logfn) { /* skip this loop if not logging */ int i; struct RSAKey *rkey; for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) { - char fingerprint[128]; - rsa_fingerprint(fingerprint, sizeof(fingerprint), rkey); + char *fingerprint = rsa_ssh1_fingerprint(rkey); plog(logctx, logfn, "returned key: %s", fingerprint); + sfree(fingerprint); } } - sfree(keylist); } break; case SSH2_AGENTC_REQUEST_IDENTITIES: @@ -342,35 +225,22 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, * Reply with SSH2_AGENT_IDENTITIES_ANSWER. */ { - int len; - void *keylist; - plog(logctx, logfn, "request: SSH2_AGENTC_REQUEST_IDENTITIES"); - ret[4] = SSH2_AGENT_IDENTITIES_ANSWER; - keylist = pageant_make_keylist2(&len); - if (len + 5 > AGENT_MAX_MSGLEN) { - sfree(keylist); - fail_reason = "output would exceed max msglen"; - goto failure; - } - PUT_32BIT(ret, len + 1); - memcpy(ret + 5, keylist, len); + put_byte(bs, SSH2_AGENT_IDENTITIES_ANSWER); + pageant_make_keylist2(bs); plog(logctx, logfn, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); if (logfn) { /* skip this loop if not logging */ int i; struct ssh2_userkey *skey; for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) { - char *fingerprint = ssh2_fingerprint(skey->alg, - skey->data); + char *fingerprint = ssh2_fingerprint(skey->key); plog(logctx, logfn, "returned key: %s %s", fingerprint, skey->comment); sfree(fingerprint); } } - - sfree(keylist); } break; case SSH1_AGENTC_RSA_CHALLENGE: @@ -382,94 +252,64 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, { struct RSAKey reqkey, *key; Bignum challenge, response; - unsigned char response_source[48], response_md5[16]; + ptrlen session_id; + unsigned response_type; + unsigned char response_md5[16]; struct MD5Context md5c; - int i, len; + int i; plog(logctx, logfn, "request: SSH1_AGENTC_RSA_CHALLENGE"); - p += 4; - i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent); - if (i < 0) { - fail_reason = "request truncated before key exponent"; - goto failure; - } - p += i; - i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus); - if (i < 0) { - freebn(reqkey.exponent); - fail_reason = "request truncated before key modulus"; - goto failure; - } - p += i; - i = ssh1_read_bignum(p, msgend - p, &challenge); - if (i < 0) { - freebn(reqkey.exponent); - freebn(reqkey.modulus); - freebn(challenge); - fail_reason = "request truncated before challenge"; - goto failure; - } - p += i; - if (msgend < p+16) { - freebn(reqkey.exponent); - freebn(reqkey.modulus); - freebn(challenge); - fail_reason = "request truncated before session id"; - goto failure; - } - memcpy(response_source + 32, p, 16); - p += 16; - if (msgend < p+4) { - freebn(reqkey.exponent); - freebn(reqkey.modulus); - freebn(challenge); - fail_reason = "request truncated before response type"; - goto failure; + response = NULL; + memset(&reqkey, 0, sizeof(reqkey)); + + get_rsa_ssh1_pub(msg, &reqkey, RSA_SSH1_EXPONENT_FIRST); + challenge = get_mp_ssh1(msg); + session_id = get_data(msg, 16); + response_type = get_uint32(msg); + + if (get_err(msg)) { + pageant_failure_msg(bs, "unable to decode request", + logctx, logfn); + goto challenge1_cleanup; } - if (GET_32BIT(p) != 1) { - freebn(reqkey.exponent); - freebn(reqkey.modulus); - freebn(challenge); - fail_reason = "response type other than 1 not supported"; - goto failure; + if (response_type != 1) { + pageant_failure_msg( + bs, "response type other than 1 not supported", + logctx, logfn); + goto challenge1_cleanup; } + if (logfn) { - char fingerprint[128]; + char *fingerprint; reqkey.comment = NULL; - rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey); + fingerprint = rsa_ssh1_fingerprint(&reqkey); plog(logctx, logfn, "requested key: %s", fingerprint); + sfree(fingerprint); } if ((key = find234(rsakeys, &reqkey, NULL)) == NULL) { - freebn(reqkey.exponent); - freebn(reqkey.modulus); - freebn(challenge); - fail_reason = "key not found"; - goto failure; + pageant_failure_msg(bs, "key not found", logctx, logfn); + goto challenge1_cleanup; } - response = rsadecrypt(challenge, key); - for (i = 0; i < 32; i++) - response_source[i] = bignum_byte(response, 31 - i); + response = rsa_ssh1_decrypt(challenge, key); MD5Init(&md5c); - MD5Update(&md5c, response_source, 48); + for (i = 0; i < 32; i++) + put_byte(&md5c, bignum_byte(response, 31 - i)); + put_data(&md5c, session_id.ptr, session_id.len); MD5Final(response_md5, &md5c); - smemclr(response_source, 48); /* burn the evidence */ - freebn(response); /* and that evidence */ - freebn(challenge); /* yes, and that evidence */ - freebn(reqkey.exponent); /* and free some memory ... */ - freebn(reqkey.modulus); /* ... while we're at it. */ - - /* - * Packet is the obvious five byte header, plus sixteen - * bytes of MD5. - */ - len = 5 + 16; - PUT_32BIT(ret, len - 4); - ret[4] = SSH1_AGENT_RSA_RESPONSE; - memcpy(ret + 5, response_md5, 16); + + put_byte(bs, SSH1_AGENT_RSA_RESPONSE); + put_data(bs, response_md5, 16); plog(logctx, logfn, "reply: SSH1_AGENT_RSA_RESPONSE"); + + challenge1_cleanup: + if (response) + freebn(response); + freebn(challenge); + freebn(reqkey.exponent); + freebn(reqkey.modulus); } break; case SSH2_AGENTC_SIGN_REQUEST: @@ -480,54 +320,31 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, */ { struct ssh2_userkey *key; - struct blob b; - const unsigned char *data; - unsigned char *signature; - int datalen, siglen, len; + ptrlen keyblob, sigdata; + strbuf *signature; plog(logctx, logfn, "request: SSH2_AGENTC_SIGN_REQUEST"); - if (msgend < p+4) { - fail_reason = "request truncated before public key"; - goto failure; - } - b.len = toint(GET_32BIT(p)); - if (b.len < 0 || b.len > msgend - (p+4)) { - fail_reason = "request truncated before public key"; - goto failure; - } - p += 4; - b.blob = p; - p += b.len; - if (msgend < p+4) { - fail_reason = "request truncated before string to sign"; - goto failure; - } - datalen = toint(GET_32BIT(p)); - p += 4; - if (datalen < 0 || datalen > msgend - p) { - fail_reason = "request truncated before string to sign"; - goto failure; - } - data = p; + keyblob = get_string(msg); + sigdata = get_string(msg); if (logfn) { - char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len); + char *fingerprint = ssh2_fingerprint_blob( + keyblob.ptr, keyblob.len); plog(logctx, logfn, "requested key: %s", fingerprint); sfree(fingerprint); } - key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); + key = find234(ssh2keys, &keyblob, cmpkeys_ssh2_asymm); if (!key) { - fail_reason = "key not found"; - goto failure; + pageant_failure_msg(bs, "key not found", logctx, logfn); + return; } - signature = key->alg->sign(key->data, (const char *)data, - datalen, &siglen); - len = 5 + 4 + siglen; - PUT_32BIT(ret, len - 4); - ret[4] = SSH2_AGENT_SIGN_RESPONSE; - PUT_32BIT(ret + 5, siglen); - memcpy(ret + 5 + 4, signature, siglen); - sfree(signature); + + signature = strbuf_new(); + ssh_key_sign(key->key, sigdata.ptr, sigdata.len, + BinarySink_UPCAST(signature)); + + put_byte(bs, SSH2_AGENT_SIGN_RESPONSE); + put_stringsb(bs, signature); plog(logctx, logfn, "reply: SSH2_AGENT_SIGN_RESPONSE"); } @@ -539,179 +356,99 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, */ { struct RSAKey *key; - char *comment; - int n, commentlen; plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY"); key = snew(struct RSAKey); memset(key, 0, sizeof(struct RSAKey)); - n = makekey(p, msgend - p, key, NULL, 1); - if (n < 0) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before public key"; - goto failure; - } - p += n; - - n = makeprivate(p, msgend - p, key); - if (n < 0) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before private key"; - goto failure; - } - p += n; + get_rsa_ssh1_pub(msg, key, RSA_SSH1_MODULUS_FIRST); + get_rsa_ssh1_priv(msg, key); /* 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. */ + key->iqmp = get_mp_ssh1(msg); + key->q = get_mp_ssh1(msg); + key->p = get_mp_ssh1(msg); - n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */ - if (n < 0) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before iqmp"; - goto failure; - } - p += n; - - n = ssh1_read_bignum(p, msgend - p, &key->q); /* p */ - if (n < 0) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before p"; - goto failure; - } - p += n; - - n = ssh1_read_bignum(p, msgend - p, &key->p); /* q */ - if (n < 0) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before q"; - goto failure; - } - p += n; + key->comment = mkstr(get_string(msg)); - if (msgend < p+4) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before key comment"; - goto failure; - } - commentlen = toint(GET_32BIT(p)); - - if (commentlen < 0 || commentlen > msgend - p) { - freersakey(key); - sfree(key); - fail_reason = "request truncated before key comment"; - goto failure; - } + if (get_err(msg)) { + pageant_failure_msg(bs, "unable to decode request", + logctx, logfn); + goto add1_cleanup; + } - comment = snewn(commentlen+1, char); - if (comment) { - memcpy(comment, p + 4, commentlen); - comment[commentlen] = '\0'; - key->comment = comment; - } + if (!rsa_verify(key)) { + pageant_failure_msg(bs, "key is invalid", logctx, logfn); + goto add1_cleanup; + } if (logfn) { - char fingerprint[128]; - rsa_fingerprint(fingerprint, sizeof(fingerprint), key); + char *fingerprint = rsa_ssh1_fingerprint(key); plog(logctx, logfn, "submitted key: %s", fingerprint); + sfree(fingerprint); } if (add234(rsakeys, key) == key) { keylist_update(); - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_SUCCESS; - + put_byte(bs, SSH_AGENT_SUCCESS); plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); + key = NULL; /* don't free it in cleanup */ } else { + pageant_failure_msg(bs, "key already present", + logctx, logfn); + } + + add1_cleanup: + if (key) { freersakey(key); sfree(key); - - fail_reason = "key already present"; - goto failure; - } + } } break; case SSH2_AGENTC_ADD_IDENTITY: + case SSH2_AGENTC_ADD_ID_CONSTRAINED: /* * Add to the list and return SSH_AGENT_SUCCESS, or * SSH_AGENT_FAILURE if the key was malformed. */ { - struct ssh2_userkey *key; - char *comment; - const char *alg; - int alglen, commlen; - int bloblen; + struct ssh2_userkey *key = NULL; + ptrlen algpl; + const ssh_keyalg *alg; plog(logctx, logfn, "request: SSH2_AGENTC_ADD_IDENTITY"); - if (msgend < p+4) { - fail_reason = "request truncated before key algorithm"; - goto failure; - } - alglen = toint(GET_32BIT(p)); - p += 4; - if (alglen < 0 || alglen > msgend - p) { - fail_reason = "request truncated before key algorithm"; - goto failure; - } - alg = (const char *)p; - p += alglen; + algpl = get_string(msg); key = snew(struct ssh2_userkey); - key->alg = find_pubkey_alg_len(alglen, alg); - if (!key->alg) { - sfree(key); - fail_reason = "algorithm unknown"; - goto failure; - } - - bloblen = msgend - p; - key->data = key->alg->openssh_createkey(key->alg, &p, &bloblen); - if (!key->data) { - sfree(key); - fail_reason = "key setup failed"; - goto failure; + key->key = NULL; + key->comment = NULL; + alg = find_pubkey_alg_len(algpl); + if (!alg) { + pageant_failure_msg(bs, "algorithm unknown", logctx, logfn); + goto add2_cleanup; } - /* - * p has been advanced by openssh_createkey, but - * certainly not _beyond_ the end of the buffer. - */ - assert(p <= msgend); + key->key = ssh_key_new_priv_openssh(alg, msg); - if (msgend < p+4) { - key->alg->freekey(key->data); - sfree(key); - fail_reason = "request truncated before key comment"; - goto failure; + if (!key->key) { + pageant_failure_msg(bs, "key setup failed", logctx, logfn); + goto add2_cleanup; } - commlen = toint(GET_32BIT(p)); - p += 4; - if (commlen < 0 || commlen > msgend - p) { - key->alg->freekey(key->data); - sfree(key); - fail_reason = "request truncated before key comment"; - goto failure; - } - comment = snewn(commlen + 1, char); - if (comment) { - memcpy(comment, p, commlen); - comment[commlen] = '\0'; - } - key->comment = comment; + key->comment = mkstr(get_string(msg)); + + if (get_err(msg)) { + pageant_failure_msg(bs, "unable to decode request", + logctx, logfn); + goto add2_cleanup; + } if (logfn) { - char *fingerprint = ssh2_fingerprint(key->alg, key->data); + char *fingerprint = ssh2_fingerprint(key->key); plog(logctx, logfn, "submitted key: %s %s", fingerprint, key->comment); sfree(fingerprint); @@ -719,18 +456,24 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, if (add234(ssh2keys, key) == key) { keylist_update(); - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_SUCCESS; + put_byte(bs, SSH_AGENT_SUCCESS); plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); - } else { - key->alg->freekey(key->data); - sfree(key->comment); - sfree(key); - fail_reason = "key already present"; - goto failure; + key = NULL; /* don't clean it up */ + } else { + pageant_failure_msg(bs, "key already present", + logctx, logfn); } + + add2_cleanup: + if (key) { + if (key->key) + ssh_key_free(key->key); + if (key->comment) + sfree(key->comment); + sfree(key); + } } break; case SSH1_AGENTC_REMOVE_RSA_IDENTITY: @@ -741,27 +484,30 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, */ { struct RSAKey reqkey, *key; - int n; plog(logctx, logfn, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); - n = makekey(p, msgend - p, &reqkey, NULL, 0); - if (n < 0) { - fail_reason = "request truncated before public key"; - goto failure; + get_rsa_ssh1_pub(msg, &reqkey, RSA_SSH1_EXPONENT_FIRST); + + if (get_err(msg)) { + pageant_failure_msg(bs, "unable to decode request", + logctx, logfn); + freebn(reqkey.exponent); + freebn(reqkey.modulus); + return; } if (logfn) { - char fingerprint[128]; + char *fingerprint; reqkey.comment = NULL; - rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey); + fingerprint = rsa_ssh1_fingerprint(&reqkey); plog(logctx, logfn, "unwanted key: %s", fingerprint); + sfree(fingerprint); } key = find234(rsakeys, &reqkey, NULL); freebn(reqkey.exponent); freebn(reqkey.modulus); - PUT_32BIT(ret, 1); if (key) { plog(logctx, logfn, "found with comment: %s", key->comment); @@ -769,12 +515,11 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, keylist_update(); freersakey(key); sfree(key); - ret[4] = SSH_AGENT_SUCCESS; + put_byte(bs, SSH_AGENT_SUCCESS); plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } else { - fail_reason = "key not found"; - goto failure; + pageant_failure_msg(bs, "key not found", logctx, logfn); } } break; @@ -786,44 +531,38 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, */ { struct ssh2_userkey *key; - struct blob b; + ptrlen blob; plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_IDENTITY"); - if (msgend < p+4) { - fail_reason = "request truncated before public key"; - goto failure; - } - b.len = toint(GET_32BIT(p)); - p += 4; + blob = get_string(msg); - if (b.len < 0 || b.len > msgend - p) { - fail_reason = "request truncated before public key"; - goto failure; + if (get_err(msg)) { + pageant_failure_msg(bs, "unable to decode request", + logctx, logfn); + return; } - b.blob = p; - p += b.len; if (logfn) { - char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len); + char *fingerprint = ssh2_fingerprint_blob(blob.ptr, blob.len); plog(logctx, logfn, "unwanted key: %s", fingerprint); sfree(fingerprint); } - key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); + key = find234(ssh2keys, &blob, cmpkeys_ssh2_asymm); if (!key) { - fail_reason = "key not found"; - goto failure; + pageant_failure_msg(bs, "key not found", logctx, logfn); + return; } plog(logctx, logfn, "found with comment: %s", key->comment); del234(ssh2keys, key); keylist_update(); - key->alg->freekey(key->data); + ssh_key_free(key->key); + sfree(key->comment); sfree(key); - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_SUCCESS; + put_byte(bs, SSH_AGENT_SUCCESS); plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } @@ -845,8 +584,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, } keylist_update(); - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_SUCCESS; + put_byte(bs, SSH_AGENT_SUCCESS); plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } @@ -862,48 +600,35 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen, while ((skey = index234(ssh2keys, 0)) != NULL) { del234(ssh2keys, skey); - skey->alg->freekey(skey->data); + ssh_key_free(skey->key); + sfree(skey->comment); sfree(skey); } keylist_update(); - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_SUCCESS; + put_byte(bs, SSH_AGENT_SUCCESS); plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } break; default: plog(logctx, logfn, "request: unknown message type %d", type); - - fail_reason = "unrecognised message"; - /* fall through */ - failure: - /* - * Unrecognised message. Return SSH_AGENT_FAILURE. - */ - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_FAILURE; - plog(logctx, logfn, "reply: SSH_AGENT_FAILURE (%s)", fail_reason); + pageant_failure_msg(bs, "unrecognised message", logctx, logfn); break; } - - *outlen = 4 + GET_32BIT(ret); - return ret; } -void *pageant_failure_msg(int *outlen) +void pageant_failure_msg(BinarySink *bs, + const char *log_reason, + void *logctx, pageant_logfn_t logfn) { - unsigned char *ret = snewn(5, unsigned char); - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_FAILURE; - *outlen = 5; - return ret; + put_byte(bs, SSH_AGENT_FAILURE); + plog(logctx, logfn, "reply: SSH_AGENT_FAILURE (%s)", log_reason); } void pageant_init(void) { - pageant_local = TRUE; + pageant_local = true; rsakeys = newtree234(cmpkeys_rsa); ssh2keys = newtree234(cmpkeys_ssh2); } @@ -928,32 +653,32 @@ int pageant_count_ssh2_keys(void) return count234(ssh2keys); } -int pageant_add_ssh1_key(struct RSAKey *rkey) +bool pageant_add_ssh1_key(struct RSAKey *rkey) { return add234(rsakeys, rkey) == rkey; } -int pageant_add_ssh2_key(struct ssh2_userkey *skey) +bool pageant_add_ssh2_key(struct ssh2_userkey *skey) { return add234(ssh2keys, skey) == skey; } -int pageant_delete_ssh1_key(struct RSAKey *rkey) +bool pageant_delete_ssh1_key(struct RSAKey *rkey) { struct RSAKey *deleted = del234(rsakeys, rkey); if (!deleted) - return FALSE; + return false; assert(deleted == rkey); - return TRUE; + return true; } -int pageant_delete_ssh2_key(struct ssh2_userkey *skey) +bool pageant_delete_ssh2_key(struct ssh2_userkey *skey) { struct ssh2_userkey *deleted = del234(ssh2keys, skey); if (!deleted) - return FALSE; + return false; assert(deleted == skey); - return TRUE; + return true; } /* ---------------------------------------------------------------------- @@ -964,45 +689,45 @@ int pageant_delete_ssh2_key(struct ssh2_userkey *skey) * Coroutine macros similar to, but simplified from, those in ssh.c. */ #define crBegin(v) { int *crLine = &v; switch(v) { case 0:; -#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } #define crGetChar(c) do \ { \ while (len == 0) { \ - *crLine =__LINE__; return 1; case __LINE__:; \ + *crLine =__LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ } while (0) struct pageant_conn_state { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - - Socket connsock; + Socket *connsock; void *logctx; pageant_logfn_t logfn; unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN]; unsigned len, got; - int real_packet; + bool real_packet; int crLine; /* for coroutine in pageant_conn_receive */ + + Plug plug; }; -static int pageant_conn_closing(Plug plug, const char *error_msg, - int error_code, int calling_back) +static void pageant_conn_closing(Plug *plug, const char *error_msg, + int error_code, bool calling_back) { - struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; + struct pageant_conn_state *pc = container_of( + plug, struct pageant_conn_state, plug); if (error_msg) plog(pc->logctx, pc->logfn, "%p: error: %s", pc, error_msg); else plog(pc->logctx, pc->logfn, "%p: connection closed", pc); sk_close(pc->connsock); sfree(pc); - return 1; } -static void pageant_conn_sent(Plug plug, int bufsize) +static void pageant_conn_sent(Plug *plug, int bufsize) { - /* struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; */ + /* struct pageant_conn_state *pc = container_of( + plug, struct pageant_conn_state, plug); */ /* * We do nothing here, because we expect that there won't be a @@ -1021,9 +746,10 @@ static void pageant_conn_log(void *logctx, const char *fmt, va_list ap) sfree(formatted); } -static int pageant_conn_receive(Plug plug, int urgent, char *data, int len) +static void pageant_conn_receive(Plug *plug, int urgent, char *data, int len) { - struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; + struct pageant_conn_state *pc = container_of( + plug, struct pageant_conn_state, plug); char c; crBegin(pc->crLine); @@ -1047,107 +773,113 @@ static int pageant_conn_receive(Plug plug, int urgent, char *data, int len) } { - void *reply; - int replylen; + strbuf *reply = strbuf_new(); + + put_uint32(reply, 0); /* length field to fill in later */ if (pc->real_packet) { - reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen, pc, - pc->logfn?pageant_conn_log:NULL); + pageant_handle_msg(BinarySink_UPCAST(reply), pc->pktbuf, pc->len, pc, + pc->logfn ? pageant_conn_log : NULL); } else { plog(pc->logctx, pc->logfn, "%p: overlong message (%u)", pc, pc->len); - plog(pc->logctx, pc->logfn, "%p: reply: SSH_AGENT_FAILURE " - "(message too long)", pc); - reply = pageant_failure_msg(&replylen); + pageant_failure_msg(BinarySink_UPCAST(reply), "message too long", pc, + pc->logfn ? pageant_conn_log : NULL); } - sk_write(pc->connsock, reply, replylen); - smemclr(reply, replylen); + + PUT_32BIT(reply->s, reply->len - 4); + sk_write(pc->connsock, reply->s, reply->len); + + strbuf_free(reply); } } - crFinish(1); + crFinishV; } struct pageant_listen_state { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - - Socket listensock; + Socket *listensock; void *logctx; pageant_logfn_t logfn; + + Plug plug; }; -static int pageant_listen_closing(Plug plug, const char *error_msg, - int error_code, int calling_back) +static void pageant_listen_closing(Plug *plug, const char *error_msg, + int error_code, bool calling_back) { - struct pageant_listen_state *pl = (struct pageant_listen_state *)plug; + struct pageant_listen_state *pl = container_of( + plug, struct pageant_listen_state, plug); if (error_msg) plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg); sk_close(pl->listensock); pl->listensock = NULL; - return 1; } -static int pageant_listen_accepting(Plug plug, +static const PlugVtable pageant_connection_plugvt = { + NULL, /* no log function, because that's for outgoing connections */ + pageant_conn_closing, + pageant_conn_receive, + pageant_conn_sent, + NULL /* no accepting function, because we've already done it */ +}; + +static int pageant_listen_accepting(Plug *plug, accept_fn_t constructor, accept_ctx_t ctx) { - static const struct plug_function_table connection_fn_table = { - NULL, /* no log function, because that's for outgoing connections */ - pageant_conn_closing, - pageant_conn_receive, - pageant_conn_sent, - NULL /* no accepting function, because we've already done it */ - }; - struct pageant_listen_state *pl = (struct pageant_listen_state *)plug; + struct pageant_listen_state *pl = container_of( + plug, struct pageant_listen_state, plug); struct pageant_conn_state *pc; const char *err; - char *peerinfo; + SocketPeerInfo *peerinfo; pc = snew(struct pageant_conn_state); - pc->fn = &connection_fn_table; + pc->plug.vt = &pageant_connection_plugvt; pc->logfn = pl->logfn; pc->logctx = pl->logctx; pc->crLine = 0; - pc->connsock = constructor(ctx, (Plug) pc); + pc->connsock = constructor(ctx, &pc->plug); if ((err = sk_socket_error(pc->connsock)) != NULL) { sk_close(pc->connsock); sfree(pc); - return TRUE; + return 1; } sk_set_frozen(pc->connsock, 0); peerinfo = sk_peer_info(pc->connsock); - if (peerinfo) { + if (peerinfo && peerinfo->log_text) { plog(pl->logctx, pl->logfn, "%p: new connection from %s", - pc, peerinfo); + pc, peerinfo->log_text); } else { plog(pl->logctx, pl->logfn, "%p: new connection", pc); } + sk_free_peer_info(peerinfo); return 0; } -struct pageant_listen_state *pageant_listener_new(void) -{ - static const struct plug_function_table listener_fn_table = { - NULL, /* no log function, because that's for outgoing connections */ - pageant_listen_closing, - NULL, /* no receive function on a listening socket */ - NULL, /* no sent function on a listening socket */ - pageant_listen_accepting - }; +static const PlugVtable pageant_listener_plugvt = { + NULL, /* no log function, because that's for outgoing connections */ + pageant_listen_closing, + NULL, /* no receive function on a listening socket */ + NULL, /* no sent function on a listening socket */ + pageant_listen_accepting +}; +struct pageant_listen_state *pageant_listener_new(Plug **plug) +{ struct pageant_listen_state *pl = snew(struct pageant_listen_state); - pl->fn = &listener_fn_table; + pl->plug.vt = &pageant_listener_plugvt; pl->logctx = NULL; pl->logfn = NULL; pl->listensock = NULL; + *plug = &pl->plug; return pl; } -void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock) +void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket *sock) { pl->listensock = sock; } @@ -1195,14 +927,16 @@ void *pageant_get_keylist1(int *length) void *ret; if (!pageant_local) { - unsigned char request[5], *response; + strbuf *request; + unsigned char *response; void *vresponse; int resplen; - request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES; - PUT_32BIT(request, 1); + request = strbuf_new_for_agent_query(); + put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); - agent_query_synchronous(request, 5, &vresponse, &resplen); response = vresponse; if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) { sfree(response); @@ -1216,7 +950,10 @@ void *pageant_get_keylist1(int *length) if (length) *length = resplen-5; } else { - ret = pageant_make_keylist1(length); + strbuf *buf = strbuf_new(); + pageant_make_keylist1(BinarySink_UPCAST(buf)); + *length = buf->len; + ret = strbuf_to_str(buf); } return ret; } @@ -1226,14 +963,16 @@ void *pageant_get_keylist2(int *length) void *ret; if (!pageant_local) { - unsigned char request[5], *response; + strbuf *request; + unsigned char *response; void *vresponse; int resplen; - request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; - PUT_32BIT(request, 1); + request = strbuf_new_for_agent_query(); + put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); - agent_query_synchronous(request, 5, &vresponse, &resplen); response = vresponse; if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) { sfree(response); @@ -1247,7 +986,10 @@ void *pageant_get_keylist2(int *length) if (length) *length = resplen-5; } else { - ret = pageant_make_keylist2(length); + strbuf *buf = strbuf_new(); + pageant_make_keylist2(BinarySink_UPCAST(buf)); + *length = buf->len; + ret = strbuf_to_str(buf); } return ret; } @@ -1257,7 +999,8 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, { struct RSAKey *rkey = NULL; struct ssh2_userkey *skey = NULL; - int needs_pass; + struct ssh2_userkey *ckey = NULL; + bool needs_pass; int ret; int attempts; char *comment; @@ -1283,64 +1026,62 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, * which may or may not be us). */ { - void *blob; + strbuf *blob = strbuf_new(); unsigned char *keylist, *p; - int i, nkeys, bloblen, keylistlen; + int i, nkeys, keylistlen; if (type == SSH_KEYTYPE_SSH1) { - if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) { + if (!rsa_ssh1_loadpub(filename, BinarySink_UPCAST(blob), NULL, &error)) { *retstr = dupprintf("Couldn't load private key (%s)", error); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } keylist = pageant_get_keylist1(&keylistlen); } else { - unsigned char *blob2; - blob = ssh2_userkey_loadpub(filename, NULL, &bloblen, - NULL, &error); - if (!blob) { + /* For our purposes we want the blob prefixed with its + * length, so add a placeholder here to fill in + * afterwards */ + put_uint32(blob, 0); + if (!ssh2_userkey_loadpub(filename, NULL, BinarySink_UPCAST(blob), + NULL, &error)) { *retstr = dupprintf("Couldn't load private key (%s)", error); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } - /* For our purposes we want the blob prefixed with its length */ - blob2 = snewn(bloblen+4, unsigned char); - PUT_32BIT(blob2, bloblen); - memcpy(blob2 + 4, blob, bloblen); - sfree(blob); - blob = blob2; - + PUT_32BIT(blob->s, blob->len - 4); keylist = pageant_get_keylist2(&keylistlen); } if (keylist) { if (keylistlen < 4) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } nkeys = toint(GET_32BIT(keylist)); if (nkeys < 0) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } p = keylist + 4; keylistlen -= 4; for (i = 0; i < nkeys; i++) { - if (!memcmp(blob, p, bloblen)) { + if (!memcmp(blob->s, p, blob->len)) { /* Key is already present; we can now leave. */ sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_OK; } /* Now skip over public blob */ if (type == SSH_KEYTYPE_SSH1) { - int n = rsa_public_blob_len(p, keylistlen); + int n = rsa_ssh1_public_blob_len(p, keylistlen); if (n < 0) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } p += n; @@ -1350,7 +1091,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, if (keylistlen < 4) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } n = GET_32BIT(p); @@ -1360,7 +1101,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, if (n < 0 || n > keylistlen) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } p += n; @@ -1372,7 +1113,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, if (keylistlen < 4) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } n = GET_32BIT(p); @@ -1382,7 +1123,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, if (n < 0 || n > keylistlen) { *retstr = dupstr("Received broken key list from agent"); sfree(keylist); - sfree(blob); + strbuf_free(blob); return PAGEANT_ACTION_FAILURE; } p += n; @@ -1393,12 +1134,12 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, sfree(keylist); } - sfree(blob); + strbuf_free(blob); } error = NULL; if (type == SSH_KEYTYPE_SSH1) - needs_pass = rsakey_encrypted(filename, &comment); + needs_pass = rsa_ssh1_encrypted(filename, &comment); else needs_pass = ssh2_userkey_encrypted(filename, &comment); attempts = 0; @@ -1436,7 +1177,7 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, this_passphrase = ""; if (type == SSH_KEYTYPE_SSH1) - ret = loadrsakey(filename, rkey, this_passphrase, &error); + ret = rsa_ssh1_loadkey(filename, rkey, this_passphrase, &error); else { skey = ssh2_load_userkey(filename, this_passphrase, &error); if (skey == SSH2_WRONG_PASSPHRASE) @@ -1469,10 +1210,42 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, } /* - * If we get here, we've succesfully loaded the key into + * If we get here, we've successfully loaded the key into * rkey/skey, but not yet added it to the agent. */ + /* + * attempt to load a certificate + */ + Filename certfilename; + certfilename.path = dupprintf("%s-cert", filename->path); + FILE* certfile = f_open(&certfilename, "rb", false); + sfree(certfilename.path); + + if (certfile) { + strbuf* certblob = strbuf_new(); + char* certalg = NULL; + char* comment = NULL; + const char* errstring = NULL; + if (openssh_loadpub(certfile, &certalg, BinarySink_UPCAST(certblob), &comment, &errstring)) { + strbuf* privblob = strbuf_new(); + ssh_key_private_blob(skey->key, BinarySink_UPCAST(privblob)); + const ssh_keyalg* alg = find_pubkey_alg(certalg); + if (alg) { + ssh_key* cert = ssh_key_new_priv(alg, ptrlen_from_strbuf(certblob), ptrlen_from_strbuf(privblob)); + if (cert) { + ckey = snew(struct ssh2_userkey); + ckey->key = cert; + ckey->comment = comment; + } + strbuf_free(privblob); + } + } + strbuf_free(certblob); + fclose(certfile); + certfile = NULL; + } + /* * If the key was successfully decrypted, save the passphrase for * use with other keys we try to load. @@ -1491,55 +1264,35 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, if (type == SSH_KEYTYPE_SSH1) { if (!pageant_local) { - unsigned char *request, *response; + strbuf *request; + unsigned char *response; void *vresponse; - int reqlen, clen, resplen; - - clen = strlen(rkey->comment); - - reqlen = 4 + 1 + /* length, message type */ - 4 + /* bit count */ - ssh1_bignum_length(rkey->modulus) + - ssh1_bignum_length(rkey->exponent) + - ssh1_bignum_length(rkey->private_exponent) + - ssh1_bignum_length(rkey->iqmp) + - ssh1_bignum_length(rkey->p) + - ssh1_bignum_length(rkey->q) + 4 + clen /* comment */ - ; - - request = snewn(reqlen, unsigned char); - - request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY; - reqlen = 5; - PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus)); - reqlen += 4; - reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus); - reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent); - reqlen += - ssh1_write_bignum(request + reqlen, - rkey->private_exponent); - reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp); - reqlen += ssh1_write_bignum(request + reqlen, rkey->p); - reqlen += ssh1_write_bignum(request + reqlen, rkey->q); - PUT_32BIT(request + reqlen, clen); - memcpy(request + reqlen + 4, rkey->comment, clen); - reqlen += 4 + clen; - PUT_32BIT(request, reqlen - 4); - - agent_query_synchronous(request, reqlen, &vresponse, &resplen); + int resplen; + + request = strbuf_new_for_agent_query(); + put_byte(request, SSH1_AGENTC_ADD_RSA_IDENTITY); + put_uint32(request, bignum_bitcount(rkey->modulus)); + put_mp_ssh1(request, rkey->modulus); + put_mp_ssh1(request, rkey->exponent); + put_mp_ssh1(request, rkey->private_exponent); + put_mp_ssh1(request, rkey->iqmp); + put_mp_ssh1(request, rkey->q); + put_mp_ssh1(request, rkey->p); + put_stringz(request, rkey->comment); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); + response = vresponse; if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { *retstr = dupstr("The already running Pageant " "refused to add the key."); freersakey(rkey); sfree(rkey); - sfree(request); sfree(response); return PAGEANT_ACTION_FAILURE; } freersakey(rkey); sfree(rkey); - sfree(request); sfree(response); } else { if (!pageant_add_ssh1_key(rkey)) { @@ -1549,53 +1302,63 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, } } else { if (!pageant_local) { - unsigned char *request, *response; + strbuf *request; + unsigned char *response; void *vresponse; - int reqlen, alglen, clen, keybloblen, resplen; - alglen = strlen(skey->alg->name); - clen = strlen(skey->comment); - - keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0); - - reqlen = 4 + 1 + /* length, message type */ - 4 + alglen + /* algorithm name */ - keybloblen + /* key data */ - 4 + clen /* comment */ - ; - - request = snewn(reqlen, unsigned char); - - request[4] = SSH2_AGENTC_ADD_IDENTITY; - reqlen = 5; - PUT_32BIT(request + reqlen, alglen); - reqlen += 4; - memcpy(request + reqlen, skey->alg->name, alglen); - reqlen += alglen; - reqlen += skey->alg->openssh_fmtkey(skey->data, - request + reqlen, - keybloblen); - PUT_32BIT(request + reqlen, clen); - memcpy(request + reqlen + 4, skey->comment, clen); - reqlen += clen + 4; - PUT_32BIT(request, reqlen - 4); - - agent_query_synchronous(request, reqlen, &vresponse, &resplen); + int resplen; + + request = strbuf_new_for_agent_query(); + put_byte(request, SSH2_AGENTC_ADD_IDENTITY); + put_stringz(request, ssh_key_ssh_id(skey->key)); + ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(request)); + put_stringz(request, skey->comment); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); + response = vresponse; if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { *retstr = dupstr("The already running Pageant " "refused to add the key."); - sfree(request); sfree(response); return PAGEANT_ACTION_FAILURE; } - sfree(request); sfree(response); + + /* add cert here if it was found */ + + if (ckey != NULL) { + request = strbuf_new_for_agent_query(); + put_byte(request, SSH2_AGENTC_ADD_IDENTITY); + put_stringz(request, ssh_key_ssh_id(ckey->key)); + ssh_key_openssh_blob(ckey->key, BinarySink_UPCAST(request)); + put_stringz(request, ckey->comment); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); + + response = vresponse; + if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { + *retstr = dupstr("The already running Pageant " + "refused to add the certificate."); + sfree(response); + return PAGEANT_ACTION_FAILURE; + } + + sfree(response); + } + } else { if (!pageant_add_ssh2_key(skey)) { - skey->alg->freekey(skey->data); - sfree(skey); /* already present, don't waste RAM */ + ssh_key_free(skey->key); + sfree(skey); /* already present, don't waste RAM */ } + + /* add cert here if it was found */ + if (ckey != NULL && !pageant_add_ssh2_key(ckey)) { + ssh_key_free(ckey->key); + sfree(ckey); /* already present, don't waste RAM */ + } + } } return PAGEANT_ACTION_OK; @@ -1604,143 +1367,93 @@ int pageant_add_keyfile(Filename *filename, const char *passphrase, int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, char **retstr) { - unsigned char *keylist, *p; + unsigned char *keylist; int i, nkeys, keylistlen; - char *comment; + ptrlen comment; struct pageant_pubkey cbkey; + BinarySource src[1]; keylist = pageant_get_keylist1(&keylistlen); - if (keylistlen < 4) { - *retstr = dupstr("Received broken SSH-1 key list from agent"); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - nkeys = toint(GET_32BIT(keylist)); - if (nkeys < 0) { - *retstr = dupstr("Received broken SSH-1 key list from agent"); - sfree(keylist); + if (!keylist) { + *retstr = dupstr("Did not receive an SSH-1 key list from agent"); return PAGEANT_ACTION_FAILURE; } - p = keylist + 4; - keylistlen -= 4; + BinarySource_BARE_INIT(src, keylist, keylistlen); + nkeys = toint(get_uint32(src)); for (i = 0; i < nkeys; i++) { struct RSAKey rkey; - char fingerprint[128]; - int n; + char *fingerprint; /* public blob and fingerprint */ memset(&rkey, 0, sizeof(rkey)); - n = makekey(p, keylistlen, &rkey, NULL, 0); - if (n < 0 || n > keylistlen) { - freersakey(&rkey); - *retstr = dupstr("Received broken SSH-1 key list from agent"); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - p += n, keylistlen -= n; - rsa_fingerprint(fingerprint, sizeof(fingerprint), &rkey); + get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST); + comment = get_string(src); - /* comment */ - if (keylistlen < 4) { + if (get_err(src)) { *retstr = dupstr("Received broken SSH-1 key list from agent"); freersakey(&rkey); sfree(keylist); return PAGEANT_ACTION_FAILURE; } - n = toint(GET_32BIT(p)); - p += 4, keylistlen -= 4; - if (n < 0 || keylistlen < n) { - *retstr = dupstr("Received broken SSH-1 key list from agent"); - freersakey(&rkey); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - comment = dupprintf("%.*s", (int)n, (const char *)p); - p += n, keylistlen -= n; - cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen); - cbkey.comment = comment; + fingerprint = rsa_ssh1_fingerprint(&rkey); + + cbkey.blob = strbuf_new(); + rsa_ssh1_public_blob(BinarySink_UPCAST(cbkey.blob), &rkey, + RSA_SSH1_EXPONENT_FIRST); + cbkey.comment = mkstr(comment); cbkey.ssh_version = 1; - callback(callback_ctx, fingerprint, comment, &cbkey); - sfree(cbkey.blob); + callback(callback_ctx, fingerprint, cbkey.comment, &cbkey); + strbuf_free(cbkey.blob); freersakey(&rkey); - sfree(comment); + sfree(cbkey.comment); + sfree(fingerprint); } sfree(keylist); - if (keylistlen != 0) { + if (get_err(src) || get_avail(src) != 0) { *retstr = dupstr("Received broken SSH-1 key list from agent"); return PAGEANT_ACTION_FAILURE; } keylist = pageant_get_keylist2(&keylistlen); - if (keylistlen < 4) { - *retstr = dupstr("Received broken SSH-2 key list from agent"); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - nkeys = toint(GET_32BIT(keylist)); - if (nkeys < 0) { - *retstr = dupstr("Received broken SSH-2 key list from agent"); - sfree(keylist); + if (!keylist) { + *retstr = dupstr("Did not receive an SSH-2 key list from agent"); return PAGEANT_ACTION_FAILURE; } - p = keylist + 4; - keylistlen -= 4; + BinarySource_BARE_INIT(src, keylist, keylistlen); + nkeys = toint(get_uint32(src)); for (i = 0; i < nkeys; i++) { + ptrlen pubblob; char *fingerprint; - int n; - /* public blob */ - if (keylistlen < 4) { - *retstr = dupstr("Received broken SSH-2 key list from agent"); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - n = toint(GET_32BIT(p)); - p += 4, keylistlen -= 4; - if (n < 0 || keylistlen < n) { - *retstr = dupstr("Received broken SSH-2 key list from agent"); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - fingerprint = ssh2_fingerprint_blob(p, n); - cbkey.blob = p; - cbkey.bloblen = n; - p += n, keylistlen -= n; + pubblob = get_string(src); + comment = get_string(src); - /* comment */ - if (keylistlen < 4) { - *retstr = dupstr("Received broken SSH-2 key list from agent"); - sfree(fingerprint); - sfree(keylist); - return PAGEANT_ACTION_FAILURE; - } - n = toint(GET_32BIT(p)); - p += 4, keylistlen -= 4; - if (n < 0 || keylistlen < n) { + if (get_err(src)) { *retstr = dupstr("Received broken SSH-2 key list from agent"); - sfree(fingerprint); sfree(keylist); return PAGEANT_ACTION_FAILURE; } - comment = dupprintf("%.*s", (int)n, (const char *)p); - p += n, keylistlen -= n; + + fingerprint = ssh2_fingerprint_blob(pubblob.ptr, pubblob.len); + cbkey.blob = strbuf_new(); + put_data(cbkey.blob, pubblob.ptr, pubblob.len); cbkey.ssh_version = 2; - cbkey.comment = comment; - callback(callback_ctx, fingerprint, comment, &cbkey); + cbkey.comment = mkstr(comment); + callback(callback_ctx, fingerprint, cbkey.comment, &cbkey); sfree(fingerprint); - sfree(comment); + sfree(cbkey.comment); } sfree(keylist); - if (keylistlen != 0) { - *retstr = dupstr("Received broken SSH-1 key list from agent"); + if (get_err(src) || get_avail(src) != 0) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); return PAGEANT_ACTION_FAILURE; } @@ -1749,26 +1462,24 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, int pageant_delete_key(struct pageant_pubkey *key, char **retstr) { - unsigned char *request, *response; - int reqlen, resplen, ret; + strbuf *request; + unsigned char *response; + int resplen, ret; void *vresponse; + request = strbuf_new_for_agent_query(); + if (key->ssh_version == 1) { - reqlen = 5 + key->bloblen; - request = snewn(reqlen, unsigned char); - PUT_32BIT(request, reqlen - 4); - request[4] = SSH1_AGENTC_REMOVE_RSA_IDENTITY; - memcpy(request + 5, key->blob, key->bloblen); + put_byte(request, SSH1_AGENTC_REMOVE_RSA_IDENTITY); + put_data(request, key->blob->s, key->blob->len); } else { - reqlen = 9 + key->bloblen; - request = snewn(reqlen, unsigned char); - PUT_32BIT(request, reqlen - 4); - request[4] = SSH2_AGENTC_REMOVE_IDENTITY; - PUT_32BIT(request + 5, key->bloblen); - memcpy(request + 9, key->blob, key->bloblen); + put_byte(request, SSH2_AGENTC_REMOVE_IDENTITY); + put_string(request, key->blob->s, key->blob->len); } - agent_query_synchronous(request, reqlen, &vresponse, &resplen); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); + response = vresponse; if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { *retstr = dupstr("Agent failed to delete key"); @@ -1777,21 +1488,22 @@ int pageant_delete_key(struct pageant_pubkey *key, char **retstr) *retstr = NULL; ret = PAGEANT_ACTION_OK; } - sfree(request); sfree(response); return ret; } int pageant_delete_all_keys(char **retstr) { - unsigned char request[5], *response; - int reqlen, resplen, success; + strbuf *request; + unsigned char *response; + int resplen; + bool success; void *vresponse; - PUT_32BIT(request, 1); - request[4] = SSH2_AGENTC_REMOVE_ALL_IDENTITIES; - reqlen = 5; - agent_query_synchronous(request, reqlen, &vresponse, &resplen); + request = strbuf_new_for_agent_query(); + put_byte(request, SSH2_AGENTC_REMOVE_ALL_IDENTITIES); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); response = vresponse; success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS); sfree(response); @@ -1800,10 +1512,10 @@ int pageant_delete_all_keys(char **retstr) return PAGEANT_ACTION_FAILURE; } - PUT_32BIT(request, 1); - request[4] = SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES; - reqlen = 5; - agent_query_synchronous(request, reqlen, &vresponse, &resplen); + request = strbuf_new_for_agent_query(); + put_byte(request, SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES); + agent_query_synchronous(request, &vresponse, &resplen); + strbuf_free(request); response = vresponse; success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS); sfree(response); @@ -1819,9 +1531,8 @@ int pageant_delete_all_keys(char **retstr) struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key) { struct pageant_pubkey *ret = snew(struct pageant_pubkey); - ret->blob = snewn(key->bloblen, unsigned char); - memcpy(ret->blob, key->blob, key->bloblen); - ret->bloblen = key->bloblen; + ret->blob = strbuf_new(); + put_data(ret->blob, key->blob->s, key->blob->len); ret->comment = key->comment ? dupstr(key->comment) : NULL; ret->ssh_version = key->ssh_version; return ret; @@ -1830,6 +1541,6 @@ struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key) void pageant_pubkey_free(struct pageant_pubkey *key) { sfree(key->comment); - sfree(key->blob); + strbuf_free(key->blob); sfree(key); } diff --git a/pageant.h b/pageant.h index 6e29f40c..e033546a 100644 --- a/pageant.h +++ b/pageant.h @@ -5,12 +5,11 @@ #include /* - * FIXME: it would be nice not to have this arbitrary limit. It's - * currently needed because the Windows Pageant IPC system needs an - * upper bound known to the client, but it's also reused as a basic - * sanity check on incoming messages' length fields. + * Upper limit on length of any agent message. Used as a basic sanity + * check on messages' length fields, and used by the Windows Pageant + * client IPC to decide how large a file mapping to allocate. */ -#define AGENT_MAX_MSGLEN 8192 +#define AGENT_MAX_MSGLEN 262144 typedef void (*pageant_logfn_t)(void *logctx, const char *fmt, va_list ap); @@ -28,21 +27,26 @@ void pageant_init(void); * Returns a fully formatted message as output, *with* its initial * length field, and sets *outlen to the full size of that message. */ -void *pageant_handle_msg(const void *msg, int msglen, int *outlen, - void *logctx, pageant_logfn_t logfn); +void pageant_handle_msg(BinarySink *bs, + const void *msg, int msglen, + void *logctx, pageant_logfn_t logfn); /* * Construct a failure response. Useful for agent front ends which * suffer a problem before they even get to pageant_handle_msg. + * + * 'log_reason' is only used if logfn is not NULL. */ -void *pageant_failure_msg(int *outlen); +void pageant_failure_msg(BinarySink *bs, + const char *log_reason, + void *logctx, pageant_logfn_t logfn); /* * Construct a list of public keys, just as the two LIST_IDENTITIES * requests would have returned them. */ -void *pageant_make_keylist1(int *length); -void *pageant_make_keylist2(int *length); +void pageant_make_keylist1(BinarySink *); +void pageant_make_keylist2(BinarySink *); /* * Accessor functions for Pageant's internal key lists. Fetch the nth @@ -56,10 +60,10 @@ struct RSAKey *pageant_nth_ssh1_key(int i); struct ssh2_userkey *pageant_nth_ssh2_key(int i); int pageant_count_ssh1_keys(void); int pageant_count_ssh2_keys(void); -int pageant_add_ssh1_key(struct RSAKey *rkey); -int pageant_add_ssh2_key(struct ssh2_userkey *skey); -int pageant_delete_ssh1_key(struct RSAKey *rkey); -int pageant_delete_ssh2_key(struct ssh2_userkey *skey); +bool pageant_add_ssh1_key(struct RSAKey *rkey); +bool pageant_add_ssh2_key(struct ssh2_userkey *skey); +bool pageant_delete_ssh1_key(struct RSAKey *rkey); +bool pageant_delete_ssh2_key(struct ssh2_userkey *skey); /* * This callback must be provided by the Pageant front end code. @@ -74,14 +78,14 @@ void keylist_update(void); /* * Functions to establish a listening socket speaking the SSH agent * protocol. Call pageant_listener_new() to set up a state; then - * create a socket using the returned pointer as a Plug; then call + * create a socket using the returned Plug; then call * pageant_listener_got_socket() to give the listening state its own * socket pointer. Also, provide a logging function later if you want * to. */ struct pageant_listen_state; -struct pageant_listen_state *pageant_listener_new(void); -void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock); +struct pageant_listen_state *pageant_listener_new(Plug **plug); +void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket *); void pageant_listener_set_logfn(struct pageant_listen_state *pl, void *logctx, pageant_logfn_t logfn); void pageant_listener_free(struct pageant_listen_state *pl); @@ -125,8 +129,7 @@ struct pageant_pubkey { /* Everything needed to identify a public key found by * pageant_enum_keys and pass it back to the agent or other code * later */ - void *blob; - int bloblen; + strbuf *blob; char *comment; int ssh_version; }; diff --git a/pgssapi.c b/pgssapi.c index 5d9ffeb4..9d33220f 100644 --- a/pgssapi.c +++ b/pgssapi.c @@ -43,7 +43,7 @@ static const gss_OID_desc oids[] = { {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 + * 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 diff --git a/pgssapi.h b/pgssapi.h index f6370c2d..74d9c774 100644 --- a/pgssapi.h +++ b/pgssapi.h @@ -24,7 +24,7 @@ typedef gss_OID const_gss_OID; /* for our prototypes below */ ******************************************************************************/ /* GSSAPI Type Definitions */ -typedef uint32 OM_uint32; +typedef uint32_t OM_uint32; typedef struct gss_OID_desc_struct { OM_uint32 length; @@ -58,6 +58,7 @@ 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. */ @@ -76,6 +77,13 @@ typedef OM_uint32 gss_qop_t; #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 @@ -256,6 +264,13 @@ typedef OM_uint32 (GSS_CC *t_gss_get_mic) 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*/, @@ -280,15 +295,37 @@ 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 */ diff --git a/pinger.c b/pinger.c index d8f110ac..ddebc727 100644 --- a/pinger.c +++ b/pinger.c @@ -1,37 +1,36 @@ /* - * pinger.c: centralised module that deals with sending TS_PING + * pinger.c: centralised module that deals with sending SS_PING * keepalives, to avoid replicating this code in multiple backends. */ #include "putty.h" -struct pinger_tag { +struct Pinger { int interval; - int pending; + bool pending; unsigned long when_set, next; - Backend *back; - void *backhandle; + Backend *backend; }; -static void pinger_schedule(Pinger pinger); +static void pinger_schedule(Pinger *pinger); static void pinger_timer(void *ctx, unsigned long now) { - Pinger pinger = (Pinger)ctx; + Pinger *pinger = (Pinger *)ctx; if (pinger->pending && now == pinger->next) { - pinger->back->special(pinger->backhandle, TS_PING); - pinger->pending = FALSE; + backend_special(pinger->backend, SS_PING, 0); + pinger->pending = false; pinger_schedule(pinger); } } -static void pinger_schedule(Pinger pinger) +static void pinger_schedule(Pinger *pinger) { unsigned long next; if (!pinger->interval) { - pinger->pending = FALSE; /* cancel any pending ping */ + pinger->pending = false; /* cancel any pending ping */ return; } @@ -41,24 +40,23 @@ static void pinger_schedule(Pinger pinger) (next - pinger->when_set) < (pinger->next - pinger->when_set)) { pinger->next = next; pinger->when_set = timing_last_clock(); - pinger->pending = TRUE; + pinger->pending = true; } } -Pinger pinger_new(Conf *conf, Backend *back, void *backhandle) +Pinger *pinger_new(Conf *conf, Backend *backend) { - Pinger pinger = snew(struct pinger_tag); + Pinger *pinger = snew(Pinger); pinger->interval = conf_get_int(conf, CONF_ping_interval); - pinger->pending = FALSE; - pinger->back = back; - pinger->backhandle = backhandle; + pinger->pending = false; + pinger->backend = backend; pinger_schedule(pinger); return pinger; } -void pinger_reconfig(Pinger pinger, Conf *oldconf, Conf *newconf) +void pinger_reconfig(Pinger *pinger, Conf *oldconf, Conf *newconf) { int newinterval = conf_get_int(newconf, CONF_ping_interval); if (conf_get_int(oldconf, CONF_ping_interval) != newinterval) { @@ -67,7 +65,7 @@ void pinger_reconfig(Pinger pinger, Conf *oldconf, Conf *newconf) } } -void pinger_free(Pinger pinger) +void pinger_free(Pinger *pinger) { expire_timer_context(pinger); sfree(pinger); diff --git a/portfwd.c b/portfwd.c index 8a73a182..eaba9820 100644 --- a/portfwd.c +++ b/portfwd.c @@ -2,35 +2,34 @@ * SSH port forwarding. */ +#include #include #include #include "putty.h" #include "ssh.h" +#include "sshchan.h" -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -struct PortForwarding { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - struct ssh_channel *c; /* channel structure held by ssh.c */ - void *backhandle; /* instance of SSH backend itself */ - /* Note that backhandle need not be filled in if c is non-NULL */ - Socket s; - int throttled, throttle_override; - int ready; - /* - * `dynamic' does double duty. It's set to 0 for an ordinary - * forwarded port, and nonzero for SOCKS-style dynamic port - * forwarding; but the nonzero values are also a state machine - * tracking where the SOCKS exchange has got to. - */ - int dynamic; +/* + * 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. @@ -38,36 +37,29 @@ struct PortForwarding { char *hostname; int port; /* - * `socksbuf' is the buffer we use to accumulate a SOCKS request. + * `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. */ - char *socksbuf; - int sockslen, sockssize; - /* - * When doing dynamic port forwarding, we can receive - * connection data before we are actually able to send it; so - * we may have to temporarily hold some in a dynamically - * allocated buffer here. - */ - void *buffer; - int buflen; -}; + strbuf *socksbuf; + size_t socksbuf_consumed; + + Plug plug; + Channel chan; +} PortForwarding; struct PortListener { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - void *backhandle; /* instance of SSH backend itself */ - Socket s; - /* - * `dynamic' is set to 0 for an ordinary forwarded port, and - * nonzero for SOCKS-style dynamic port forwarding. - */ - int dynamic; + 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) @@ -75,8 +67,6 @@ static struct PortForwarding *new_portfwd_state(void) struct PortForwarding *pf = snew(struct PortForwarding); pf->hostname = NULL; pf->socksbuf = NULL; - pf->sockslen = pf->sockssize = 0; - pf->buffer = NULL; return pf; } @@ -85,8 +75,8 @@ static void free_portfwd_state(struct PortForwarding *pf) if (!pf) return; sfree(pf->hostname); - sfree(pf->socksbuf); - sfree(pf->buffer); + if (pf->socksbuf) + strbuf_free(pf->socksbuf); sfree(pf); } @@ -105,34 +95,37 @@ static void free_portlistener_state(struct PortListener *pl) sfree(pl); } -static void pfd_log(Plug plug, int type, SockAddr addr, int port, +static void pfd_log(Plug *plug, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { /* we have to dump these since we have no interface to logging.c */ } -static void pfl_log(Plug plug, int type, SockAddr addr, int port, +static void pfl_log(Plug *plug, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { /* we have to dump these since we have no interface to logging.c */ } -static int pfd_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +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 = (struct PortForwarding *) plug; + struct PortForwarding *pf = + container_of(plug, struct PortForwarding, plug); if (error_msg) { /* * Socket error. Slam the connection instantly shut. */ if (pf->c) { - sshfwd_unclean_close(pf->c, error_msg); + 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_unclean_close's call + * clean ourself up without sshfwd_initiate_close's call * back to pfd_close. */ pfd_close(pf); @@ -145,234 +138,271 @@ static int pfd_closing(Plug plug, const char *error_msg, int error_code, if (pf->c) sshfwd_write_eof(pf->c); } - - return 1; } -static int pfl_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +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); - return 1; } -static void wrap_send_port_open(void *channel, const char *hostname, int port, - Socket s) +static SshChannel *wrap_lportfwd_open( + ConnectionLayer *cl, const char *hostname, int port, + Socket *s, Channel *chan) { - char *peerinfo, *description; - peerinfo = sk_peer_info(s); - if (peerinfo) { - description = dupprintf("forwarding from %s", peerinfo); - sfree(peerinfo); + 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"); } - ssh_send_port_open(channel, hostname, port, description); + toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan); + sk_free_peer_info(pi); + sfree(description); + return toret; } -static int pfd_receive(Plug plug, int urgent, char *data, int len) -{ - struct PortForwarding *pf = (struct PortForwarding *) plug; - if (pf->dynamic) { - while (len--) { - if (pf->sockslen >= pf->sockssize) { - pf->sockssize = pf->sockslen * 5 / 4 + 256; - pf->socksbuf = sresize(pf->socksbuf, pf->sockssize, char); - } - pf->socksbuf[pf->sockslen++] = *data++; - - /* - * Now check what's in the buffer to see if it's a - * valid and complete message in the SOCKS exchange. - */ - if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 4) && - pf->socksbuf[0] == 4) { - /* - * SOCKS 4. - */ - if (pf->dynamic == 1) - pf->dynamic = 0x4000; - if (pf->sockslen < 2) - continue; /* don't have command code yet */ - if (pf->socksbuf[1] != 1) { - /* Not CONNECT. */ - /* Send back a SOCKS 4 error before closing. */ - char data[8]; - memset(data, 0, sizeof(data)); - data[1] = 91; /* generic `request rejected' */ - sk_write(pf->s, data, 8); - pfd_close(pf); - return 1; - } - if (pf->sockslen <= 8) - continue; /* haven't started user/hostname */ - if (pf->socksbuf[pf->sockslen-1] != 0) - continue; /* haven't _finished_ user/hostname */ - /* - * Now we have a full SOCKS 4 request. Check it to - * see if it's a SOCKS 4A request. - */ - if (pf->socksbuf[4] == 0 && pf->socksbuf[5] == 0 && - pf->socksbuf[6] == 0 && pf->socksbuf[7] != 0) { - /* - * It's SOCKS 4A. So if we haven't yet - * collected the host name, we should continue - * waiting for data in order to do so; if we - * have, we can go ahead. - */ - int len; - if (pf->dynamic == 0x4000) { - pf->dynamic = 0x4001; - pf->sockslen = 8; /* reset buffer to overwrite name */ - continue; - } - pf->socksbuf[0] = 0; /* reply version code */ - pf->socksbuf[1] = 90; /* request granted */ - sk_write(pf->s, pf->socksbuf, 8); - len = pf->sockslen - 8; - pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2); - pf->hostname = snewn(len+1, char); - pf->hostname[len] = '\0'; - memcpy(pf->hostname, pf->socksbuf + 8, len); - goto connect; - } else { - /* - * It's SOCKS 4, which means we should format - * the IP address into the hostname string and - * then just go. - */ - pf->socksbuf[0] = 0; /* reply version code */ - pf->socksbuf[1] = 90; /* request granted */ - sk_write(pf->s, pf->socksbuf, 8); - pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+2); - pf->hostname = dupprintf("%d.%d.%d.%d", - (unsigned char)pf->socksbuf[4], - (unsigned char)pf->socksbuf[5], - (unsigned char)pf->socksbuf[6], - (unsigned char)pf->socksbuf[7]); - goto connect; - } - } - - if ((pf->dynamic == 1 || (pf->dynamic >> 12) == 5) && - pf->socksbuf[0] == 5) { - /* - * SOCKS 5. - */ - if (pf->dynamic == 1) - pf->dynamic = 0x5000; - - if (pf->dynamic == 0x5000) { - int i, method; - char data[2]; - /* - * We're receiving a set of method identifiers. - */ - if (pf->sockslen < 2) - continue; /* no method count yet */ - if (pf->sockslen < 2 + (unsigned char)pf->socksbuf[1]) - continue; /* no methods yet */ - method = 0xFF; /* invalid */ - for (i = 0; i < (unsigned char)pf->socksbuf[1]; i++) - if (pf->socksbuf[2+i] == 0) { - method = 0;/* no auth */ - break; - } - data[0] = 5; - data[1] = method; - sk_write(pf->s, data, 2); - pf->dynamic = 0x5001; - pf->sockslen = 0; /* re-empty the buffer */ - continue; - } - - if (pf->dynamic == 0x5001) { - /* - * We're receiving a SOCKS request. - */ - unsigned char reply[10]; /* SOCKS5 atyp=1 reply */ - int atype, alen = 0; - - /* - * Pre-fill reply packet. - * In all cases, we set BND.{HOST,ADDR} to 0.0.0.0:0 - * (atyp=1) in the reply; if we succeed, we don't know - * the right answers, and if we fail, they should be - * ignored. - */ - memset(reply, 0, lenof(reply)); - reply[0] = 5; /* VER */ - reply[3] = 1; /* ATYP = 1 (IPv4, 0.0.0.0:0) */ - - if (pf->sockslen < 6) continue; - atype = (unsigned char)pf->socksbuf[3]; - if (atype == 1) /* IPv4 address */ - alen = 4; - if (atype == 4) /* IPv6 address */ - alen = 16; - if (atype == 3) /* domain name has leading length */ - alen = 1 + (unsigned char)pf->socksbuf[4]; - if (pf->sockslen < 6 + alen) continue; - if (pf->socksbuf[1] != 1 || pf->socksbuf[2] != 0) { - /* Not CONNECT or reserved field nonzero - error */ - reply[1] = 1; /* generic failure */ - sk_write(pf->s, (char *) reply, lenof(reply)); - pfd_close(pf); - return 1; - } - /* - * Now we have a viable connect request. Switch - * on atype. - */ - pf->port = GET_16BIT_MSB_FIRST(pf->socksbuf+4+alen); - if (atype == 1) { - /* REP=0 (success) already */ - sk_write(pf->s, (char *) reply, lenof(reply)); - pf->hostname = dupprintf("%d.%d.%d.%d", - (unsigned char)pf->socksbuf[4], - (unsigned char)pf->socksbuf[5], - (unsigned char)pf->socksbuf[6], - (unsigned char)pf->socksbuf[7]); - goto connect; - } else if (atype == 3) { - /* REP=0 (success) already */ - sk_write(pf->s, (char *) reply, lenof(reply)); - pf->hostname = snewn(alen, char); - pf->hostname[alen-1] = '\0'; - memcpy(pf->hostname, pf->socksbuf + 5, alen-1); - goto connect; - } else { - /* - * Unknown address type. (FIXME: support IPv6!) - */ - reply[1] = 8; /* atype not supported */ - sk_write(pf->s, (char *) reply, lenof(reply)); - pfd_close(pf); - return 1; - } - } - } - - /* - * If we get here without either having done `continue' - * or `goto connect', it must be because there is no - * sensible interpretation of what's in our buffer. So - * close the connection rudely. - */ - pfd_close(pf); - return 1; - } - return 1; +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, char *data, int 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: + assert(0 && "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. */ - connect: - sfree(pf->socksbuf); - pf->socksbuf = NULL; /* * Freeze the socket until the SSH server confirms the @@ -380,178 +410,166 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len) */ sk_set_frozen(pf->s, 1); - pf->c = new_sock_channel(pf->backhandle, pf); - if (pf->c == NULL) { - pfd_close(pf); - return 1; - } else { - /* asks to forward to the specified host/port for this */ - wrap_send_port_open(pf->c, pf->hostname, pf->port, pf->s); - } - pf->dynamic = 0; - - /* - * If there's any data remaining in our current buffer, - * save it to be sent on pfd_confirm(). - */ - if (len > 0) { - pf->buffer = snewn(len, char); - memcpy(pf->buffer, data, len); - pf->buflen = len; - } - } - if (pf->ready) { - if (sshfwd_write(pf->c, data, len) > 0) { - pf->throttled = 1; - sk_set_frozen(pf->s, 1); - } + pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s, + &pf->chan); } - return 1; + if (pf->ready) + sshfwd_write(pf->c, data, len); } -static void pfd_sent(Plug plug, int bufsize) +static void pfd_sent(Plug *plug, int bufsize) { - struct PortForwarding *pf = (struct PortForwarding *) plug; + struct PortForwarding *pf = + container_of(plug, struct PortForwarding, plug); if (pf->c) sshfwd_unthrottle(pf->c, bufsize); } -/* - * 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 *pfd_connect(struct PortForwarding **pf_ret, char *hostname,int port, - void *c, Conf *conf, int addressfamily) -{ - static const struct plug_function_table fn_table = { - pfd_log, - pfd_closing, - pfd_receive, - pfd_sent, - NULL - }; - - SockAddr addr; - const char *err; - char *dummy_realhost; +static const PlugVtable PortForwarding_plugvt = { + pfd_log, + pfd_closing, + pfd_receive, + pfd_sent, + NULL +}; + +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 int pfd_send(Channel *chan, bool is_stderr, const void *data, int 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 struct ChannelVtable PortForwarding_channelvt = { + pfd_chan_free, + pfd_open_confirmation, + pfd_open_failure, + pfd_send, + pfd_send_eof, + pfd_set_input_wanted, + pfd_log_close_msg, + chan_default_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + chan_no_request_response, +}; + +Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug) +{ struct PortForwarding *pf; - /* - * Try to find host. - */ - addr = name_lookup(hostname, port, &dummy_realhost, 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; - } + 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; - /* - * Open socket. - */ - pf = *pf_ret = new_portfwd_state(); - pf->fn = &fn_table; - pf->throttled = pf->throttle_override = 0; - pf->ready = 1; - pf->c = c; - pf->backhandle = NULL; /* we shouldn't need this */ - pf->dynamic = 0; + pf->c = NULL; - pf->s = new_connection(addr, dummy_realhost, port, - 0, 1, 0, 0, (Plug) pf, conf); - sfree(dummy_realhost); - if ((err = sk_socket_error(pf->s)) != NULL) { - char *err_ret = dupstr(err); - sk_close(pf->s); - free_portfwd_state(pf); - *pf_ret = NULL; - return err_ret; - } + pf->cl = cl; + pf->input_wanted = true; + pf->ready = false; - return NULL; + 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) +static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) { - static const struct plug_function_table fn_table = { - pfd_log, - pfd_closing, - pfd_receive, - pfd_sent, - NULL - }; + struct PortListener *pl = container_of(p, struct PortListener, plug); struct PortForwarding *pf; - struct PortListener *pl; - Socket s; + Channel *chan; + Plug *plug; + Socket *s; const char *err; - pl = (struct PortListener *)p; - pf = new_portfwd_state(); - pf->fn = &fn_table; - - pf->c = NULL; - pf->backhandle = pl->backhandle; - - pf->s = s = constructor(ctx, (Plug) pf); + chan = portfwd_raw_new(pl->cl, &plug); + s = constructor(ctx, plug); if ((err = sk_socket_error(s)) != NULL) { - free_portfwd_state(pf); - return err != NULL; + portfwd_raw_free(chan); + return 1; } - pf->throttled = pf->throttle_override = 0; - pf->ready = 0; + pf = container_of(chan, struct PortForwarding, chan); - if (pl->dynamic) { - pf->dynamic = 1; + 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, 0); /* we want to receive SOCKS _now_! */ } else { - pf->dynamic = 0; pf->hostname = dupstr(pl->hostname); pf->port = pl->port; - pf->c = new_sock_channel(pl->backhandle, pf); - - if (pf->c == NULL) { - free_portfwd_state(pf); - return 1; - } else { - /* asks to forward to the specified host/port for this */ - wrap_send_port_open(pf->c, pf->hostname, pf->port, s); - } + portfwd_raw_setup( + chan, s, + wrap_lportfwd_open(pl->cl, pf->hostname, pf->port, s, &pf->chan)); } return 0; } +static const PlugVtable PortListener_plugvt = { + pfl_log, + pfl_closing, + NULL, /* recv */ + NULL, /* send */ + 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. */ -char *pfl_listen(char *desthost, int destport, char *srcaddr, - int port, void *backhandle, Conf *conf, - struct PortListener **pl_ret, int address_family) -{ - static const struct plug_function_table fn_table = { - pfl_log, - pfl_closing, - NULL, /* recv */ - NULL, /* send */ - pfl_accepting - }; - +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; @@ -559,17 +577,17 @@ char *pfl_listen(char *desthost, int destport, char *srcaddr, * Open socket. */ pl = *pl_ret = new_portlistener_state(); - pl->fn = &fn_table; + pl->plug.vt = &PortListener_plugvt; if (desthost) { pl->hostname = dupstr(desthost); pl->port = destport; - pl->dynamic = 0; + pl->is_dynamic = false; } else - pl->dynamic = 1; - pl->backhandle = backhandle; + pl->is_dynamic = true; + pl->cl = cl; - pl->s = new_listener(srcaddr, port, (Plug) pl, - !conf_get_int(conf, CONF_lport_acceptall), + 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); @@ -582,7 +600,12 @@ char *pfl_listen(char *desthost, int destport, char *srcaddr, return NULL; } -void pfd_close(struct PortForwarding *pf) +static char *pfd_log_close_msg(Channel *chan) +{ + return dupstr("Forwarded port closed"); +} + +static void pfd_close(struct PortForwarding *pf) { if (!pf) return; @@ -594,7 +617,7 @@ void pfd_close(struct PortForwarding *pf) /* * Terminate a listener. */ -void pfl_terminate(struct PortListener *pl) +static void pfl_terminate(struct PortListener *pl) { if (!pl) return; @@ -603,50 +626,548 @@ void pfl_terminate(struct PortListener *pl) free_portlistener_state(pl); } -void pfd_unthrottle(struct PortForwarding *pf) +static void pfd_set_input_wanted(Channel *chan, bool wanted) { - if (!pf) - return; - - pf->throttled = 0; - sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + pf->input_wanted = wanted; + sk_set_frozen(pf->s, !pf->input_wanted); } -void pfd_override_throttle(struct PortForwarding *pf, int enable) +static void pfd_chan_free(Channel *chan) { - if (!pf) - return; - - pf->throttle_override = enable; - sk_set_frozen(pf->s, pf->throttled || pf->throttle_override); + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); + pfd_close(pf); } /* * Called to send data down the raw connection. */ -int pfd_send(struct PortForwarding *pf, char *data, int len) +static int pfd_send(Channel *chan, bool is_stderr, const void *data, int len) { - if (pf == NULL) - return 0; + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); return sk_write(pf->s, data, len); } -void pfd_send_eof(struct PortForwarding *pf) +static void pfd_send_eof(Channel *chan) { + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); sk_write_eof(pf->s); } -void pfd_confirm(struct PortForwarding *pf) +static void pfd_open_confirmation(Channel *chan) { - if (pf == NULL) - return; + assert(chan->vt == &PortForwarding_channelvt); + PortForwarding *pf = container_of(chan, PortForwarding, chan); - pf->ready = 1; + pf->ready = true; sk_set_frozen(pf->s, 0); sk_write(pf->s, NULL, 0); - if (pf->buffer) { - sshfwd_write(pf->c, pf->buffer, pf->buflen); - sfree(pf->buffer); - pf->buffer = NULL; + 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; +} + +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); + } else if (pfr->local) { + pfl_terminate(pfr->local); + } + + 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/pproxy.c b/pproxy.c index 34e569bc..4b08606e 100644 --- a/pproxy.c +++ b/pproxy.c @@ -8,10 +8,10 @@ #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) +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 index 52006794..0aa9cf35 100644 --- a/proxy.c +++ b/proxy.c @@ -9,7 +9,6 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "putty.h" #include "network.h" #include "proxy.h" @@ -23,7 +22,7 @@ * Call this when proxy negotiation is complete, so that this * socket can begin working normally. */ -void proxy_activate (Proxy_Socket p) +void proxy_activate (ProxySocket *p) { void *data; int len; @@ -34,7 +33,7 @@ void proxy_activate (Proxy_Socket p) /* we want to ignore new receive events until we have sent * all of our buffered receive data. */ - sk_set_frozen(p->sub_socket, 1); + sk_set_frozen(p->sub_socket, true); /* how many bytes of output have we buffered? */ output_before = bufchain_size(&p->pending_oob_output_data) + @@ -73,32 +72,32 @@ void proxy_activate (Proxy_Socket p) * unfreezing the actual underlying socket. */ if (!p->freeze) - sk_set_frozen((Socket)p, 0); + sk_set_frozen(&p->sock, 0); } /* basic proxy socket functions */ -static Plug sk_proxy_plug (Socket s, Plug p) +static Plug *sk_proxy_plug (Socket *s, Plug *p) { - Proxy_Socket ps = (Proxy_Socket) s; - Plug ret = ps->plug; + 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) +static void sk_proxy_close (Socket *s) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); sk_close(ps->sub_socket); sk_addr_free(ps->remote_addr); sfree(ps); } -static int sk_proxy_write (Socket s, const char *data, int len) +static int sk_proxy_write (Socket *s, const void *data, int len) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->state != PROXY_STATE_ACTIVE) { bufchain_add(&ps->pending_output_data, data, len); @@ -107,9 +106,9 @@ static int sk_proxy_write (Socket s, const char *data, int len) return sk_write(ps->sub_socket, data, len); } -static int sk_proxy_write_oob (Socket s, const char *data, int len) +static int sk_proxy_write_oob (Socket *s, const void *data, int len) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->state != PROXY_STATE_ACTIVE) { bufchain_clear(&ps->pending_output_data); @@ -120,31 +119,31 @@ static int sk_proxy_write_oob (Socket s, const char *data, int len) return sk_write_oob(ps->sub_socket, data, len); } -static void sk_proxy_write_eof (Socket s) +static void sk_proxy_write_eof (Socket *s) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->state != PROXY_STATE_ACTIVE) { - ps->pending_eof = 1; + ps->pending_eof = true; return; } sk_write_eof(ps->sub_socket); } -static void sk_proxy_flush (Socket s) +static void sk_proxy_flush (Socket *s) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->state != PROXY_STATE_ACTIVE) { - ps->pending_flush = 1; + ps->pending_flush = true; return; } sk_flush(ps->sub_socket); } -static void sk_proxy_set_frozen (Socket s, int is_frozen) +static void sk_proxy_set_frozen (Socket *s, bool is_frozen) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->state != PROXY_STATE_ACTIVE) { ps->freeze = is_frozen; @@ -181,9 +180,9 @@ static void sk_proxy_set_frozen (Socket s, int 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) { - Proxy_Socket ps = (Proxy_Socket) s; + ProxySocket *ps = container_of(s, ProxySocket, sock); if (ps->error != NULL || ps->sub_socket == NULL) { return ps->error; } @@ -192,35 +191,32 @@ static const char * sk_proxy_socket_error (Socket s) /* basic proxy plug functions */ -static void plug_proxy_log(Plug plug, int type, SockAddr addr, int port, +static void plug_proxy_log(Plug *plug, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { - Proxy_Plug pp = (Proxy_Plug) plug; - Proxy_Socket ps = pp->proxy_socket; + ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); plug_log(ps->plug, type, addr, port, error_msg, error_code); } -static int plug_proxy_closing (Plug p, const char *error_msg, - int error_code, int calling_back) +static void plug_proxy_closing (Plug *p, const char *error_msg, + int error_code, bool calling_back) { - Proxy_Plug pp = (Proxy_Plug) p; - Proxy_Socket ps = pp->proxy_socket; + 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; - return ps->negotiate(ps, PROXY_CHANGE_CLOSING); + ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } else { + plug_closing(ps->plug, error_msg, error_code, calling_back); } - return plug_closing(ps->plug, error_msg, - error_code, calling_back); } -static int plug_proxy_receive (Plug p, int urgent, char *data, int len) +static void plug_proxy_receive (Plug *p, int urgent, char *data, int len) { - Proxy_Plug pp = (Proxy_Plug) p; - Proxy_Socket ps = pp->proxy_socket; + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); if (ps->state != PROXY_STATE_ACTIVE) { /* we will lose the urgentness of this data, but since most, @@ -228,18 +224,18 @@ static int plug_proxy_receive (Plug p, int urgent, char *data, int len) * process, hopefully it won't affect the protocol above us */ bufchain_add(&ps->pending_input_data, data, len); - ps->receive_urgent = urgent; + ps->receive_urgent = (urgent != 0); ps->receive_data = data; ps->receive_len = len; - return ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } else { + plug_receive(ps->plug, urgent, data, len); } - return plug_receive(ps->plug, urgent, data, len); } -static void plug_proxy_sent (Plug p, int bufsize) +static void plug_proxy_sent (Plug *p, int bufsize) { - Proxy_Plug pp = (Proxy_Plug) p; - Proxy_Socket ps = pp->proxy_socket; + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); if (ps->state != PROXY_STATE_ACTIVE) { ps->sent_bufsize = bufsize; @@ -249,11 +245,10 @@ static void plug_proxy_sent (Plug p, int bufsize) plug_sent(ps->plug, bufsize); } -static int plug_proxy_accepting(Plug p, +static int plug_proxy_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) { - Proxy_Plug pp = (Proxy_Plug) p; - Proxy_Socket ps = pp->proxy_socket; + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); if (ps->state != PROXY_STATE_ACTIVE) { ps->accepting_constructor = constructor; @@ -267,7 +262,7 @@ static int plug_proxy_accepting(Plug p, * This function can accept a NULL pointer as `addr', in which case * it will only check the host name. */ -int proxy_for_destination (SockAddr addr, const char *hostname, +bool proxy_for_destination (SockAddr *addr, const char *hostname, int port, Conf *conf) { int s = 0, e = 0; @@ -282,16 +277,16 @@ int proxy_for_destination (SockAddr addr, const char *hostname, * them. */ if (addr && sk_address_is_special_local(addr)) - return 0; /* do not proxy */ + return false; /* do not proxy */ /* * Check the host name and IP against the hard-coded * representations of `localhost'. */ - if (!conf_get_int(conf, CONF_even_proxy_localhost) && + if (!conf_get_bool(conf, CONF_even_proxy_localhost) && (sk_hostname_is_local(hostname) || (addr && sk_address_is_local(addr)))) - return 0; /* do not proxy */ + return false; /* do not proxy */ /* we want a string representation of the IP address for comparisons */ if (addr) { @@ -329,25 +324,27 @@ int proxy_for_destination (SockAddr addr, const char *hostname, if ((addr && strnicmp(hostip + hostip_len - (e - s - 1), exclude_list + s + 1, e - s - 1) == 0) || strnicmp(hostname + hostname_len - (e - s - 1), - exclude_list + s + 1, e - s - 1) == 0) - return 0; /* IP/hostname range excluded. do not use proxy. */ - + 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) - return 0; /* IP/hostname range excluded. do not use proxy. */ - + 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 0; /* IP/hostname excluded. do not use proxy. */ + return false; /* IP/hostname excluded. do not use proxy. */ if (strnicmp(hostname, exclude_list + s, e - s) == 0) - return 0; /* IP/hostname excluded. do not use proxy. */ + return false; /* IP/hostname excluded. do not use proxy. */ } s = e; @@ -359,7 +356,7 @@ int proxy_for_destination (SockAddr addr, const char *hostname, } /* no matches in the exclude list, so use the proxy */ - return 1; + return true; } static char *dns_log_msg(const char *host, int addressfamily, @@ -371,69 +368,62 @@ static char *dns_log_msg(const char *host, int addressfamily, ""), reason); } -SockAddr name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, void *frontend, +SockAddr *name_lookup(const char *host, int port, char **canonicalname, + Conf *conf, int addressfamily, LogContext *logctx, const char *reason) { - char *logmsg; if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && do_proxy_dns(conf) && proxy_for_destination(NULL, host, port, conf)) { - if (frontend) { - logmsg = dupprintf("Leaving host lookup to proxy of \"%s\"" - " (for %s)", host, reason); - logevent(frontend, logmsg); - sfree(logmsg); - } + if (logctx) + logeventf(logctx, "Leaving host lookup to proxy of \"%s\"" + " (for %s)", host, reason); *canonicalname = dupstr(host); return sk_nonamelookup(host); } else { - if (frontend) { - logmsg = dns_log_msg(host, addressfamily, reason); - logevent(frontend, logmsg); - sfree(logmsg); - } + if (logctx) + logevent_and_free( + logctx, dns_log_msg(host, addressfamily, reason)); return sk_namelookup(host, canonicalname, addressfamily); } } -Socket new_connection(SockAddr addr, const char *hostname, - int port, int privport, - int oobinline, int nodelay, int keepalive, - Plug plug, Conf *conf) +static const struct SocketVtable ProxySocket_sockvt = { + sk_proxy_plug, + sk_proxy_close, + sk_proxy_write, + sk_proxy_write_oob, + sk_proxy_write_eof, + sk_proxy_flush, + sk_proxy_set_frozen, + sk_proxy_socket_error, + NULL, /* peer_info */ +}; + +static const struct PlugVtable ProxySocket_plugvt = { + plug_proxy_log, + plug_proxy_closing, + plug_proxy_receive, + plug_proxy_sent, + 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) { - static const struct socket_function_table socket_fn_table = { - sk_proxy_plug, - sk_proxy_close, - sk_proxy_write, - sk_proxy_write_oob, - sk_proxy_write_eof, - sk_proxy_flush, - sk_proxy_set_frozen, - sk_proxy_socket_error, - NULL, /* peer_info */ - }; - - static const struct plug_function_table plug_fn_table = { - plug_proxy_log, - plug_proxy_closing, - plug_proxy_receive, - plug_proxy_sent, - plug_proxy_accepting - }; - if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && proxy_for_destination(addr, hostname, port, conf)) { - Proxy_Socket ret; - Proxy_Plug pplug; - SockAddr proxy_addr; + ProxySocket *ret; + SockAddr *proxy_addr; char *proxy_canonical_name; const char *proxy_type; - Socket sret; + Socket *sret; int type; if ((sret = platform_new_connection(addr, hostname, port, privport, @@ -442,17 +432,18 @@ Socket new_connection(SockAddr addr, const char *hostname, NULL) return sret; - ret = snew(struct Socket_proxy_tag); - ret->fn = &socket_fn_table; + 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_flush = 0; - ret->pending_eof = 0; - ret->freeze = 0; + ret->pending_flush = false; + ret->pending_eof = false; + ret->freeze = false; bufchain_init(&ret->pending_input_data); bufchain_init(&ret->pending_output_data); @@ -477,7 +468,7 @@ Socket new_connection(SockAddr addr, const char *hostname, proxy_type = "Telnet"; } else { ret->error = "Proxy error: Unknown proxy method"; - return (Socket) ret; + return &ret->sock; } { @@ -490,12 +481,6 @@ Socket new_connection(SockAddr addr, const char *hostname, sfree(logmsg); } - /* create the proxy plug to map calls from the actual - * socket into our proxy socket layer */ - pplug = snew(struct Plug_proxy_tag); - pplug->fn = &plug_fn_table; - pplug->proxy_socket = ret; - { char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), conf_get_int(conf, CONF_addressfamily), @@ -510,9 +495,8 @@ Socket new_connection(SockAddr addr, const char *hostname, conf_get_int(conf, CONF_addressfamily)); if (sk_addr_error(proxy_addr) != NULL) { ret->error = "Proxy error: Unable to resolve proxy host name"; - sfree(pplug); sk_addr_free(proxy_addr); - return (Socket)ret; + return &ret->sock; } sfree(proxy_canonical_name); @@ -532,23 +516,23 @@ Socket new_connection(SockAddr addr, const char *hostname, ret->sub_socket = sk_new(proxy_addr, conf_get_int(conf, CONF_proxy_port), privport, oobinline, - nodelay, keepalive, (Plug) pplug); + nodelay, keepalive, &ret->plugimpl); if (sk_socket_error(ret->sub_socket) != NULL) - return (Socket) ret; + return &ret->sock; /* start the proxy negotiation process... */ sk_set_frozen(ret->sub_socket, 0); ret->negotiate(ret, PROXY_CHANGE_NEW); - return (Socket) ret; + 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, - int local_host_only, Conf *conf, int addressfamily) +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. @@ -595,7 +579,7 @@ static int get_line_end (char * data, int len) return -1; } -int proxy_http_negotiate (Proxy_Socket p, int change) +int proxy_http_negotiate (ProxySocket *p, int change) { if (p->state == PROXY_STATE_NEW) { /* we are just beginning the proxy negotiate process, @@ -644,9 +628,9 @@ int proxy_http_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { @@ -780,7 +764,7 @@ int proxy_http_negotiate (Proxy_Socket p, int change) */ /* SOCKS version 4 */ -int proxy_socks4_negotiate (Proxy_Socket p, int change) +int proxy_socks4_negotiate (ProxySocket *p, int change) { if (p->state == PROXY_CHANGE_NEW) { @@ -794,47 +778,38 @@ int proxy_socks4_negotiate (Proxy_Socket p, int change) * user ID (variable length, null terminated string) */ - int length, type, namelen; - char *command, addr[4], hostname[512]; - char *username; - - type = sk_addrtype(p->remote_addr); - if (type == ADDRTYPE_IPV6) { + 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"; - return 1; - } else if (type == ADDRTYPE_IPV4) { - namelen = 0; - sk_addrcopy(p->remote_addr, addr); - } else { /* type == ADDRTYPE_NAME */ - assert(type == ADDRTYPE_NAME); - sk_getaddr(p->remote_addr, hostname, lenof(hostname)); - namelen = strlen(hostname) + 1; /* include the NUL */ - addr[0] = addr[1] = addr[2] = 0; - addr[3] = 1; + strbuf_free(command); + return 1; } - username = conf_get_str(p->conf, CONF_proxy_username); - length = strlen(username) + namelen + 9; - command = snewn(length, char); - strcpy(command + 8, username); - - command[0] = 4; /* version 4 */ - command[1] = 1; /* CONNECT command */ - - /* port */ - command[2] = (char) (p->remote_port >> 8) & 0xff; - command[3] = (char) p->remote_port & 0xff; - - /* address */ - memcpy(command + 4, addr, 4); - - /* hostname */ - memcpy(command + 8 + strlen(username) + 1, - hostname, namelen); - - sk_write(p->sub_socket, command, length); - sfree(username); - sfree(command); + 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; @@ -847,9 +822,9 @@ int proxy_socks4_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { @@ -940,7 +915,7 @@ int proxy_socks4_negotiate (Proxy_Socket p, int change) } /* SOCKS version 5 */ -int proxy_socks5_negotiate (Proxy_Socket p, int change) +int proxy_socks5_negotiate (ProxySocket *p, int change) { if (p->state == PROXY_CHANGE_NEW) { @@ -955,26 +930,30 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change) * 0x03 = CHAP */ - char command[5]; + strbuf *command; char *username, *password; - int len; + int method_count_offset, methods_start; - command[0] = 5; /* version 5 */ + 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]) { - command[2] = 0x00; /* no authentication */ - len = 3; - proxy_socks5_offerencryptedauth (command, &len); - command[len++] = 0x02; /* username/password */ - command[1] = len - 2; /* Number of methods supported */ - } else { - command[1] = 1; /* one methods supported: */ - command[2] = 0x00; /* no authentication */ - len = 3; + proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); + put_byte(command, 0x02); /* username/password */ } - sk_write(p->sub_socket, command, len); + 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; @@ -987,9 +966,9 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { @@ -1112,36 +1091,40 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change) * dest. port (2 bytes) [network order] */ - char command[512]; - int len; - int type; - - type = sk_addrtype(p->remote_addr); - if (type == ADDRTYPE_IPV4) { - len = 10; /* 4 hdr + 4 addr + 2 trailer */ - command[3] = 1; /* IPv4 */ - sk_addrcopy(p->remote_addr, command+4); - } else if (type == ADDRTYPE_IPV6) { - len = 22; /* 4 hdr + 16 addr + 2 trailer */ - command[3] = 4; /* IPv6 */ - sk_addrcopy(p->remote_addr, command+4); - } else { - assert(type == ADDRTYPE_NAME); - command[3] = 3; - sk_getaddr(p->remote_addr, command+5, 256); - command[4] = strlen(command+5); - len = 7 + command[4]; /* 4 hdr, 1 len, N addr, 2 trailer */ + 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; } - command[0] = 5; /* version 5 */ - command[1] = 1; /* CONNECT command */ - command[2] = 0x00; + put_uint16(command, p->remote_port); - /* port */ - command[len-2] = (char) (p->remote_port >> 8) & 0xff; - command[len-1] = (char) p->remote_port & 0xff; + sk_write(p->sub_socket, command->s, command->len); - sk_write(p->sub_socket, command, len); + strbuf_free(command); p->state = 3; return 1; @@ -1240,23 +1223,25 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change) } if (p->state == 5) { - char *username = conf_get_str(p->conf, CONF_proxy_username); - char *password = conf_get_str(p->conf, CONF_proxy_password); + 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]) { - char userpwbuf[255 + 255 + 3]; - int ulen, plen; - ulen = strlen(username); - if (ulen > 255) ulen = 255; - if (ulen < 1) ulen = 1; - plen = strlen(password); - if (plen > 255) plen = 255; - if (plen < 1) plen = 1; - userpwbuf[0] = 1; /* version number of subnegotiation */ - userpwbuf[1] = ulen; - memcpy(userpwbuf+2, username, ulen); - userpwbuf[ulen+2] = plen; - memcpy(userpwbuf+ulen+3, password, plen); - sk_write(p->sub_socket, userpwbuf, ulen + plen + 3); + strbuf *auth = strbuf_new(); + 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 " @@ -1288,7 +1273,7 @@ int proxy_socks5_negotiate (Proxy_Socket p, int change) * standardised or at all well-defined.) */ -char *format_telnet_command(SockAddr addr, int port, Conf *conf) +char *format_telnet_command(SockAddr *addr, int port, Conf *conf) { char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); char *ret = NULL; @@ -1506,7 +1491,7 @@ char *format_telnet_command(SockAddr addr, int port, Conf *conf) #undef ENSURE } -int proxy_telnet_negotiate (Proxy_Socket p, int change) +int proxy_telnet_negotiate (ProxySocket *p, int change) { if (p->state == PROXY_CHANGE_NEW) { char *formatted_cmd; @@ -1561,9 +1546,9 @@ int proxy_telnet_negotiate (Proxy_Socket p, int change) * a socket close, then some error must have occurred. we'll * just pass those errors up to the backend. */ - return plug_closing(p->plug, p->closing_error_msg, - p->closing_error_code, - p->closing_calling_back); + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ } if (change == PROXY_CHANGE_SENT) { diff --git a/proxy.h b/proxy.h index 2e2324c0..2ecde43a 100644 --- a/proxy.h +++ b/proxy.h @@ -13,24 +13,21 @@ #define PROXY_ERROR_GENERAL 8000 #define PROXY_ERROR_UNEXPECTED 8001 -typedef struct Socket_proxy_tag * Proxy_Socket; - -struct Socket_proxy_tag { - const struct socket_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ +typedef struct ProxySocket ProxySocket; +struct ProxySocket { const char *error; - Socket sub_socket; - Plug plug; - SockAddr remote_addr; + Socket *sub_socket; + Plug *plug; + SockAddr *remote_addr; int remote_port; bufchain pending_output_data; bufchain pending_oob_output_data; - int pending_flush; + bool pending_flush; bufchain pending_input_data; - int pending_eof; + bool pending_eof; #define PROXY_STATE_NEW -1 #define PROXY_STATE_ACTIVE 0 @@ -40,10 +37,10 @@ struct Socket_proxy_tag { * of the initialization/setup/negotiation with the * proxy server. */ - int freeze; /* should we freeze the underlying socket when - * we are done with the proxy negotiation? this - * simply caches the value of sk_set_frozen calls. - */ + 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 @@ -58,7 +55,7 @@ struct Socket_proxy_tag { * and further the proxy negotiation process. */ - int (*negotiate) (Proxy_Socket /* this */, int /* change type */); + int (*negotiate) (ProxySocket * /* this */, int /* change type */); /* current arguments of plug handlers * (for use by proxy's negotiate function) @@ -67,10 +64,10 @@ struct Socket_proxy_tag { /* closing */ const char *closing_error_msg; int closing_error_code; - int closing_calling_back; + bool closing_calling_back; /* receive */ - int receive_urgent; + bool receive_urgent; char *receive_data; int receive_len; @@ -89,37 +86,30 @@ struct Socket_proxy_tag { int chap_num_attributes_processed; int chap_current_attribute; int chap_current_datalen; -}; - -typedef struct Plug_proxy_tag * Proxy_Plug; - -struct Plug_proxy_tag { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - - Proxy_Socket proxy_socket; + Socket sock; + Plug plugimpl; }; -extern void proxy_activate (Proxy_Socket); +extern void proxy_activate (ProxySocket *); -extern int proxy_http_negotiate (Proxy_Socket, int); -extern int proxy_telnet_negotiate (Proxy_Socket, int); -extern int proxy_socks4_negotiate (Proxy_Socket, int); -extern int proxy_socks5_negotiate (Proxy_Socket, int); +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); +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(char *command, int *len); -extern int proxy_socks5_handlechap (Proxy_Socket p); -extern int proxy_socks5_selectchap(Proxy_Socket p); +extern void proxy_socks5_offerencryptedauth(BinarySink *); +extern int proxy_socks5_handlechap (ProxySocket *); +extern int proxy_socks5_selectchap(ProxySocket *); #endif diff --git a/pscp.c b/pscp.c index 454ec084..592df7a9 100644 --- a/pscp.c +++ b/pscp.c @@ -25,28 +25,26 @@ #include "ssh.h" #include "sftp.h" #include "storage.h" -#include "int64.h" - -static int list = 0; -static int verbose = 0; -static int recursive = 0; -static int preserve = 0; -static int targetshouldbedirectory = 0; -static int statistics = 1; + +static bool list = false; +static bool verbose = false; +static bool recursive = false; +static bool preserve = false; +static bool targetshouldbedirectory = false; +static bool statistics = true; static int prev_stats_len = 0; -static int scp_unsafe_mode = 0; +static bool scp_unsafe_mode = false; static int errs = 0; -static int try_scp = 1; -static int try_sftp = 1; -static int main_cmd_is_sftp = 0; -static int fallback_cmd_is_sftp = 0; -static int using_sftp = 0; -static int uploading = 0; - -static Backend *back; -static void *backhandle; -static Conf *conf; -int sent_eof = FALSE; +static bool try_scp = true; +static bool try_sftp = true; +static bool main_cmd_is_sftp = false; +static bool fallback_cmd_is_sftp = false; +static bool using_sftp = false; +static bool uploading = false; + +static Backend *backend; +Conf *conf; +bool sent_eof = false; static void source(const char *src); static void rsource(const char *src); @@ -60,7 +58,30 @@ const char *const appname = "PSCP"; */ #define MAX_SCP_BUFSIZE 16384 -void ldisc_echoedit_update(void *handle) { } +void ldisc_echoedit_update(Ldisc *ldisc) { } + +static int pscp_output(Seat *, bool is_stderr, const void *, int); +static bool pscp_eof(Seat *); + +static const SeatVtable pscp_seat_vt = { + pscp_output, + pscp_eof, + filexfer_get_userpass_input, + nullseat_notify_remote_exit, + console_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + console_verify_ssh_host_key, + console_confirm_weak_crypto_primitive, + console_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + nullseat_get_window_pixel_size, +}; +static Seat pscp_seat[1] = {{ &pscp_seat_vt }}; static void tell_char(FILE *stream, char c) { @@ -75,79 +96,33 @@ static void tell_str(FILE *stream, const char *str) tell_char(stream, str[i]); } -static void tell_user(FILE *stream, const char *fmt, ...) +static void abandon_stats(void) { - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - va_end(ap); - str2 = dupcat(str, "\n", NULL); - sfree(str); - tell_str(stream, str2); - sfree(str2); + /* + * Output a \n to stdout (which is where we've been sending + * transfer statistics) so that the cursor will move to the next + * line. We should do this before displaying any other kind of + * output like an error message. + */ + if (prev_stats_len) { + putchar('\n'); + fflush(stdout); + prev_stats_len = 0; + } } -/* - * Print an error message and perform a fatal exit. - */ -void fatalbox(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - tell_str(stderr, str2); - sfree(str2); - errs++; - - cleanup_exit(1); -} -void modalfatalbox(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - tell_str(stderr, str2); - sfree(str2); - errs++; - - cleanup_exit(1); -} -void nonfatal(const char *fmt, ...) +static void tell_user(FILE *stream, const char *fmt, ...) { char *str, *str2; va_list ap; va_start(ap, fmt); str = dupvprintf(fmt, ap); - str2 = dupcat("Error: ", str, "\n", NULL); - sfree(str); va_end(ap); - tell_str(stderr, str2); - sfree(str2); - errs++; -} -void connection_fatal(void *frontend, const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); + str2 = dupcat(str, "\n", NULL); sfree(str); - va_end(ap); - tell_str(stderr, str2); + abandon_stats(); + tell_str(stream, str2); sfree(str2); - errs++; - - cleanup_exit(1); } /* @@ -165,15 +140,16 @@ void agent_schedule_callback(void (*callback)(void *, void *, int), * is available. * * To do this, we repeatedly call the SSH protocol module, with our - * own trap in from_backend() to catch the data that comes back. We - * do this until we have enough data. + * own pscp_output() function to catch the data that comes back. We do + * this until we have enough data. */ static unsigned char *outptr; /* where to put the data */ static unsigned outlen; /* how much data required */ static unsigned char *pending = NULL; /* any spare data */ static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ -int from_backend(void *frontend, int is_stderr, const char *data, int datalen) +static int pscp_output(Seat *seat, bool is_stderr, + const void *data, int datalen) { unsigned char *p = (unsigned char *) data; unsigned len = (unsigned) datalen; @@ -211,16 +187,7 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen) return 0; } -int from_backend_untrusted(void *frontend_handle, const char *data, int len) -{ - /* - * No "untrusted" output should get here (the way the code is - * currently, it's all diverted by FLAG_STDERR). - */ - assert(!"Unexpected call to from_backend_untrusted()"); - return 0; /* not reached */ -} -int from_backend_eof(void *frontend) +static bool pscp_eof(Seat *seat) { /* * We usually expect to be the party deciding when to close the @@ -229,12 +196,12 @@ int from_backend_eof(void *frontend) * downloading rather than uploading. */ if ((using_sftp || uploading) && !sent_eof) { - connection_fatal(frontend, - "Received unexpected end-of-file from server"); + seat_connection_fatal( + pscp_seat, "Received unexpected end-of-file from server"); } - return FALSE; + return false; } -static int ssh_scp_recv(unsigned char *buf, int len) +static bool ssh_scp_recv(void *buf, int len) { outptr = buf; outlen = len; @@ -258,15 +225,15 @@ static int ssh_scp_recv(unsigned char *buf, int len) pending = NULL; } if (outlen == 0) - return len; + return true; } while (outlen > 0) { - if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) - return 0; /* doom */ + if (backend_exitcode(backend) >= 0 || ssh_sftp_loop_iteration() < 0) + return false; /* doom */ } - return len; + return true; } /* @@ -274,8 +241,8 @@ static int ssh_scp_recv(unsigned char *buf, int len) */ static void ssh_scp_init(void) { - while (!back->sendok(backhandle)) { - if (back->exitcode(backhandle) >= 0) { + while (!backend_sendok(backend)) { + if (backend_exitcode(backend) >= 0) { errs++; return; } @@ -286,7 +253,7 @@ static void ssh_scp_init(void) } /* Work out which backend we ended up using. */ - if (!ssh_fallback_cmd(backhandle)) + if (!ssh_fallback_cmd(backend)) using_sftp = main_cmd_is_sftp; else using_sftp = fallback_cmd_is_sftp; @@ -311,15 +278,16 @@ static void bump(const char *fmt, ...) va_end(ap); str2 = dupcat(str, "\n", NULL); sfree(str); + abandon_stats(); tell_str(stderr, str2); sfree(str2); errs++; - if (back != NULL && back->connected(backhandle)) { + if (backend && backend_connected(backend)) { char ch; - back->special(backhandle, TS_EOF); - sent_eof = TRUE; - ssh_scp_recv((unsigned char *) &ch, 1); + backend_special(backend, SS_EOF, 0); + sent_eof = true; + ssh_scp_recv(&ch, 1); } cleanup_exit(1); @@ -329,7 +297,7 @@ static void bump(const char *fmt, ...) * Wait for the reply to a single SFTP request. Parallels the same * function in psftp.c (but isn't centralised into sftp.c because the * latter module handles SFTP only and shouldn't assume that SFTP is - * the only thing going on by calling connection_fatal). + * the only thing going on by calling seat_connection_fatal). */ struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) { @@ -338,13 +306,17 @@ struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) sftp_register(req); pktin = sftp_recv(); - if (pktin == NULL) - connection_fatal(NULL, "did not receive SFTP response packet " - "from server"); + if (pktin == NULL) { + seat_connection_fatal( + pscp_seat, "did not receive SFTP response packet from server"); + } rreq = sftp_find_request(pktin); - if (rreq != req) - connection_fatal(NULL, "unable to understand SFTP response packet " - "from server: %s", fxp_error()); + if (rreq != req) { + seat_connection_fatal( + pscp_seat, + "unable to understand SFTP response packet from server: %s", + fxp_error()); + } return pktin; } @@ -355,7 +327,7 @@ static void do_cmd(char *host, char *user, char *cmd) { const char *err; char *realhost; - void *logctx; + LogContext *logctx; if (host == NULL || host[0] == '\0') bump("Empty host name"); @@ -465,9 +437,9 @@ static void do_cmd(char *host, char *user, char *cmd) * things like SCP and SFTP: agent forwarding, port forwarding, * X forwarding. */ - conf_set_int(conf, CONF_x11_forward, 0); - conf_set_int(conf, CONF_agentfwd, 0); - conf_set_int(conf, CONF_ssh_simple, TRUE); + conf_set_bool(conf, CONF_x11_forward, false); + conf_set_bool(conf, CONF_agentfwd, false); + conf_set_bool(conf, CONF_ssh_simple, true); { char *key; while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) @@ -483,19 +455,19 @@ static void do_cmd(char *host, char *user, char *cmd) conf_set_str(conf, CONF_remote_cmd2, ""); if (try_sftp) { /* First choice is SFTP subsystem. */ - main_cmd_is_sftp = 1; + main_cmd_is_sftp = true; conf_set_str(conf, CONF_remote_cmd, "sftp"); - conf_set_int(conf, CONF_ssh_subsys, TRUE); + conf_set_bool(conf, CONF_ssh_subsys, true); if (try_scp) { /* Fallback is to use the provided scp command. */ - fallback_cmd_is_sftp = 0; + fallback_cmd_is_sftp = false; conf_set_str(conf, CONF_remote_cmd2, cmd); - conf_set_int(conf, CONF_ssh_subsys2, FALSE); + conf_set_bool(conf, CONF_ssh_subsys2, false); } else { /* Since we're not going to try SCP, we may as well try * harder to find an SFTP server, since in the current * implementation we have a spare slot. */ - fallback_cmd_is_sftp = 1; + fallback_cmd_is_sftp = true; /* see psftp.c for full explanation of this kludge */ conf_set_str(conf, CONF_remote_cmd2, "test -x /usr/lib/sftp-server &&" @@ -503,31 +475,27 @@ static void do_cmd(char *host, char *user, char *cmd) "test -x /usr/local/lib/sftp-server &&" " exec /usr/local/lib/sftp-server\n" "exec sftp-server"); - conf_set_int(conf, CONF_ssh_subsys2, FALSE); + conf_set_bool(conf, CONF_ssh_subsys2, false); } } else { /* Don't try SFTP at all; just try the scp command. */ - main_cmd_is_sftp = 0; + main_cmd_is_sftp = false; conf_set_str(conf, CONF_remote_cmd, cmd); - conf_set_int(conf, CONF_ssh_subsys, FALSE); + conf_set_bool(conf, CONF_ssh_subsys, false); } - conf_set_int(conf, CONF_nopty, TRUE); - - back = &ssh_backend; + conf_set_bool(conf, CONF_nopty, true); - logctx = log_init(NULL, conf); - console_provide_logctx(logctx); + logctx = log_init(default_logpolicy, conf); platform_psftp_pre_conn_setup(); - err = back->init(NULL, &backhandle, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, 0, - conf_get_int(conf, CONF_tcp_keepalives)); + err = backend_init(&ssh_backend, pscp_seat, &backend, logctx, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_bool(conf, CONF_tcp_keepalives)); if (err != NULL) bump("ssh_init: %s", err); - back->provide_logctx(backhandle, logctx); ssh_scp_init(); if (verbose && realhost != NULL && errs == 0) tell_user(stderr, "Connected to %s", realhost); @@ -537,7 +505,7 @@ static void do_cmd(char *host, char *user, char *cmd) /* * Update statistic information about current file. */ -static void print_stats(const char *name, uint64 size, uint64 done, +static void print_stats(const char *name, uint64_t size, uint64_t done, time_t start, time_t now) { float ratebs; @@ -546,43 +514,35 @@ static void print_stats(const char *name, uint64 size, uint64 done, int pct; int len; int elap; - double donedbl; - double sizedbl; elap = (unsigned long) difftime(now, start); if (now > start) - ratebs = (float) (uint64_to_double(done) / elap); + ratebs = (float)done / elap; else - ratebs = (float) uint64_to_double(done); + ratebs = (float)done; if (ratebs < 1.0) - eta = (unsigned long) (uint64_to_double(uint64_subtract(size, done))); - else { - eta = (unsigned long) - ((uint64_to_double(uint64_subtract(size, done)) / ratebs)); - } + eta = size - done; + else + eta = (unsigned long)((size - done) / ratebs); etastr = dupprintf("%02ld:%02ld:%02ld", eta / 3600, (eta % 3600) / 60, eta % 60); - donedbl = uint64_to_double(done); - sizedbl = uint64_to_double(size); - pct = (int) (100 * (donedbl * 1.0 / sizedbl)); + pct = (int) (100.0 * done / size); { - char donekb[40]; /* divide by 1024 to provide kB */ - uint64_decimal(uint64_shift_right(done, 10), donekb); - len = printf("\r%-25.25s | %s kB | %5.1f kB/s | ETA: %8s | %3d%%", - name, - donekb, ratebs / 1024.0, etastr, pct); + len = printf("\r%-25.25s | %"PRIu64" kB | %5.1f kB/s | " + "ETA: %8s | %3d%%", name, done >> 10, + ratebs / 1024.0, etastr, pct); if (len < prev_stats_len) printf("%*s", prev_stats_len - len, ""); prev_stats_len = len; - if (uint64_compare(done, size) == 0) - printf("\n"); + if (done == size) + abandon_stats(); fflush(stdout); } @@ -612,7 +572,7 @@ static char *colon(char *str) /* * Determine whether a string is entirely composed of dots. */ -static int is_dots(char *str) +static bool is_dots(char *str) { return str[strspn(str, ".")] == '\0'; } @@ -626,7 +586,7 @@ static int response(void) char ch, resp, rbuf[2048]; int p; - if (ssh_scp_recv((unsigned char *) &resp, 1) <= 0) + if (!ssh_scp_recv(&resp, 1)) bump("Lost connection"); p = 0; @@ -639,7 +599,7 @@ static int response(void) case 1: /* error */ case 2: /* fatal error */ do { - if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + if (!ssh_scp_recv(&ch, 1)) bump("Protocol error: Lost connection"); rbuf[p++] = ch; } while (p < sizeof(rbuf) && ch != '\n'); @@ -653,18 +613,18 @@ static int response(void) } } -int sftp_recvdata(char *buf, int len) +bool sftp_recvdata(char *buf, int len) { - return ssh_scp_recv((unsigned char *) buf, len); + return ssh_scp_recv(buf, len); } -int sftp_senddata(char *buf, int len) +bool sftp_senddata(char *buf, int len) { - back->send(backhandle, buf, len); - return 1; + backend_send(backend, buf, len); + return true; } int sftp_sendbuffer(void) { - return back->sendbuffer(backhandle); + return backend_sendbuffer(backend); } /* ---------------------------------------------------------------------- @@ -764,19 +724,19 @@ static struct scp_sftp_dirstack { int namepos, namelen; char *dirpath; char *wildcard; - int matched_something; /* wildcard match set was non-empty */ + bool matched_something; /* wildcard match set was non-empty */ } *scp_sftp_dirstack_head; static char *scp_sftp_remotepath, *scp_sftp_currentname; static char *scp_sftp_wildcard; -static int scp_sftp_targetisdir, scp_sftp_donethistarget; -static int scp_sftp_preserve, scp_sftp_recursive; +static bool scp_sftp_targetisdir, scp_sftp_donethistarget; +static bool scp_sftp_preserve, scp_sftp_recursive; static unsigned long scp_sftp_mtime, scp_sftp_atime; -static int scp_has_times; +static bool scp_has_times; static struct fxp_handle *scp_sftp_filehandle; static struct fxp_xfer *scp_sftp_xfer; -static uint64 scp_sftp_fileoffset; +static uint64_t scp_sftp_fileoffset; -int scp_source_setup(const char *target, int shouldbedir) +int scp_source_setup(const char *target, bool shouldbedir) { if (using_sftp) { /* @@ -786,7 +746,7 @@ int scp_source_setup(const char *target, int shouldbedir) struct sftp_packet *pktin; struct sftp_request *req; struct fxp_attrs attrs; - int ret; + bool ret; if (!fxp_init()) { tell_user(stderr, "unable to initialise SFTP: %s", fxp_error()); @@ -799,7 +759,7 @@ int scp_source_setup(const char *target, int shouldbedir) ret = fxp_stat_recv(pktin, req, &attrs); if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) - scp_sftp_targetisdir = 0; + scp_sftp_targetisdir = false; else scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0; @@ -809,7 +769,7 @@ int scp_source_setup(const char *target, int shouldbedir) scp_sftp_remotepath = dupstr(target); - scp_has_times = 0; + scp_has_times = false; } else { (void) response(); } @@ -821,8 +781,8 @@ int scp_send_errmsg(char *str) if (using_sftp) { /* do nothing; we never need to send our errors to the server */ } else { - back->send(backhandle, "\001", 1);/* scp protocol error prefix */ - back->send(backhandle, str, strlen(str)); + backend_send(backend, "\001", 1);/* scp protocol error prefix */ + backend_send(backend, str, strlen(str)); } return 0; /* can't fail */ } @@ -832,17 +792,17 @@ int scp_send_filetimes(unsigned long mtime, unsigned long atime) if (using_sftp) { scp_sftp_mtime = mtime; scp_sftp_atime = atime; - scp_has_times = 1; + scp_has_times = true; return 0; } else { char buf[80]; sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime); - back->send(backhandle, buf, strlen(buf)); + backend_send(backend, buf, strlen(buf)); return response(); } } -int scp_send_filename(const char *name, uint64 size, int permissions) +int scp_send_filename(const char *name, uint64_t size, int permissions) { if (using_sftp) { char *fullname; @@ -872,21 +832,20 @@ int scp_send_filename(const char *name, uint64 size, int permissions) errs++; return 1; } - scp_sftp_fileoffset = uint64_make(0, 0); + scp_sftp_fileoffset = 0; scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle, scp_sftp_fileoffset); sfree(fullname); return 0; } else { - char buf[40]; - char sizestr[40]; - uint64_decimal(size, sizestr); + char *buf; if (permissions < 0) permissions = 0644; - sprintf(buf, "C%04o %s ", (int)(permissions & 07777), sizestr); - back->send(backhandle, buf, strlen(buf)); - back->send(backhandle, name, strlen(name)); - back->send(backhandle, "\n", 1); + buf = dupprintf("C%04o %"PRIu64" ", (int)(permissions & 07777), size); + backend_send(backend, buf, strlen(buf)); + sfree(buf); + backend_send(backend, name, strlen(name)); + backend_send(backend, "\n", 1); return response(); } } @@ -915,10 +874,10 @@ int scp_send_filedata(char *data, int len) xfer_upload_data(scp_sftp_xfer, data, len); - scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len); + scp_sftp_fileoffset += len; return 0; } else { - int bufsize = back->send(backhandle, data, len); + int bufsize = backend_send(backend, data, len); /* * If the network transfer is backing up - that is, the @@ -929,7 +888,7 @@ int scp_send_filedata(char *data, int len) while (bufsize > MAX_SCP_BUFSIZE) { if (ssh_sftp_loop_iteration() < 0) return 1; - bufsize = back->sendbuffer(backhandle); + bufsize = backend_sendbuffer(backend); } return 0; @@ -942,11 +901,10 @@ int scp_send_finish(void) struct fxp_attrs attrs; struct sftp_packet *pktin; struct sftp_request *req; - int ret; while (!xfer_done(scp_sftp_xfer)) { pktin = sftp_recv(); - ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); + int ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin); if (ret <= 0) { tell_user(stderr, "error while writing: %s", fxp_error()); if (ret == INT_MIN) /* pktin not even freed */ @@ -966,7 +924,7 @@ int scp_send_finish(void) attrs.mtime = scp_sftp_mtime; req = fxp_fsetstat_send(scp_sftp_filehandle, attrs); pktin = sftp_wait_for_reply(req); - ret = fxp_fsetstat_recv(pktin, req); + bool ret = fxp_fsetstat_recv(pktin, req); if (!ret) { tell_user(stderr, "unable to set file times: %s", fxp_error()); errs++; @@ -975,10 +933,10 @@ int scp_send_finish(void) req = fxp_close_send(scp_sftp_filehandle); pktin = sftp_wait_for_reply(req); fxp_close_recv(pktin, req); - scp_has_times = 0; + scp_has_times = false; return 0; } else { - back->send(backhandle, "", 1); + backend_send(backend, "", 1); return response(); } } @@ -1005,7 +963,7 @@ int scp_send_dirname(const char *name, int modes) struct fxp_attrs attrs; struct sftp_packet *pktin; struct sftp_request *req; - int ret; + bool ret; if (scp_sftp_targetisdir) { fullname = dupcat(scp_sftp_remotepath, "/", name, NULL); @@ -1020,7 +978,7 @@ int scp_send_dirname(const char *name, int modes) * exists and is a directory we will assume we were either * successful or it didn't matter. */ - req = fxp_mkdir_send(fullname); + req = fxp_mkdir_send(fullname, NULL); pktin = sftp_wait_for_reply(req); ret = fxp_mkdir_recv(pktin, req); @@ -1048,9 +1006,9 @@ int scp_send_dirname(const char *name, int modes) } else { char buf[40]; sprintf(buf, "D%04o 0 ", modes); - back->send(backhandle, buf, strlen(buf)); - back->send(backhandle, name, strlen(name)); - back->send(backhandle, "\n", 1); + backend_send(backend, buf, strlen(buf)); + backend_send(backend, name, strlen(name)); + backend_send(backend, "\n", 1); return response(); } } @@ -1061,7 +1019,7 @@ int scp_send_enddir(void) sfree(scp_sftp_remotepath); return 0; } else { - back->send(backhandle, "E\n", 2); + backend_send(backend, "E\n", 2); return response(); } } @@ -1072,7 +1030,7 @@ int scp_send_enddir(void) * right at the start, whereas scp_sink_init is called to * initialise every level of recursion in the protocol. */ -int scp_sink_setup(const char *source, int preserve, int recursive) +int scp_sink_setup(const char *source, bool preserve, bool recursive) { if (using_sftp) { char *newsource; @@ -1097,7 +1055,7 @@ int scp_sink_setup(const char *source, int preserve, int recursive) sfree(newsource); dupsource = dupstr(source); - lastpart = stripslashes(dupsource, 0); + lastpart = stripslashes(dupsource, false); wildcard = dupstr(lastpart); *lastpart = '\0'; if (*dupsource && dupsource[1]) { @@ -1150,7 +1108,7 @@ int scp_sink_setup(const char *source, int preserve, int recursive) } scp_sftp_preserve = preserve; scp_sftp_recursive = recursive; - scp_sftp_donethistarget = 0; + scp_sftp_donethistarget = false; scp_sftp_dirstack_head = NULL; } return 0; @@ -1159,7 +1117,7 @@ int scp_sink_setup(const char *source, int preserve, int recursive) int scp_sink_init(void) { if (!using_sftp) { - back->send(backhandle, "", 1); + backend_send(backend, "", 1); } return 0; } @@ -1173,8 +1131,8 @@ struct scp_sink_action { char *buf; /* will need freeing after use */ char *name; /* filename or dirname (not ENDDIR) */ long permissions; /* access permissions (not ENDDIR) */ - uint64 size; /* file size (not ENDDIR) */ - int settime; /* 1 if atime and mtime are filled */ + uint64_t size; /* file size (not ENDDIR) */ + bool settime; /* true if atime and mtime are filled */ unsigned long atime, mtime; /* access times for the file */ }; @@ -1182,11 +1140,11 @@ int scp_get_sink_action(struct scp_sink_action *act) { if (using_sftp) { char *fname; - int must_free_fname; + bool must_free_fname; struct fxp_attrs attrs; struct sftp_packet *pktin; struct sftp_request *req; - int ret; + bool ret; if (!scp_sftp_dirstack_head) { if (!scp_sftp_donethistarget) { @@ -1194,8 +1152,8 @@ int scp_get_sink_action(struct scp_sink_action *act) * Simple case: we are only dealing with one file. */ fname = scp_sftp_remotepath; - must_free_fname = 0; - scp_sftp_donethistarget = 1; + must_free_fname = false; + scp_sftp_donethistarget = true; } else { /* * Even simpler case: one file _which we've done_. @@ -1217,11 +1175,11 @@ int scp_get_sink_action(struct scp_sink_action *act) head->names[head->namepos].filename)))) head->namepos++; /* skip . and .. */ if (head->namepos < head->namelen) { - head->matched_something = 1; + head->matched_something = true; fname = dupcat(head->dirpath, "/", head->names[head->namepos++].filename, NULL); - must_free_fname = 1; + must_free_fname = true; } else { /* * We've come to the end of the list; pop it off @@ -1386,7 +1344,7 @@ int scp_get_sink_action(struct scp_sink_action *act) newitem->dirpath = dupstr(fname); if (scp_sftp_wildcard) { newitem->wildcard = scp_sftp_wildcard; - newitem->matched_something = 0; + newitem->matched_something = false; scp_sftp_wildcard = NULL; } else { newitem->wildcard = NULL; @@ -1397,17 +1355,17 @@ int scp_get_sink_action(struct scp_sink_action *act) act->action = SCP_SINK_RETRY; } else { act->action = SCP_SINK_DIR; - act->buf = dupstr(stripslashes(fname, 0)); + act->buf = dupstr(stripslashes(fname, false)); act->name = act->buf; - act->size = uint64_make(0,0); /* duhh, it's a directory */ + act->size = 0; /* duhh, it's a directory */ act->permissions = 07777 & attrs.permissions; if (scp_sftp_preserve && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { act->atime = attrs.atime; act->mtime = attrs.mtime; - act->settime = 1; + act->settime = true; } else - act->settime = 0; + act->settime = false; } return 0; @@ -1416,20 +1374,20 @@ int scp_get_sink_action(struct scp_sink_action *act) * It's a file. Return SCP_SINK_FILE. */ act->action = SCP_SINK_FILE; - act->buf = dupstr(stripslashes(fname, 0)); + act->buf = dupstr(stripslashes(fname, false)); act->name = act->buf; if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { act->size = attrs.size; } else - act->size = uint64_make(ULONG_MAX,ULONG_MAX); /* no idea */ + act->size = UINT64_MAX; /* no idea */ act->permissions = 07777 & attrs.permissions; if (scp_sftp_preserve && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { act->atime = attrs.atime; act->mtime = attrs.mtime; - act->settime = 1; + act->settime = true; } else - act->settime = 0; + act->settime = false; if (must_free_fname) scp_sftp_currentname = fname; else @@ -1438,24 +1396,24 @@ int scp_get_sink_action(struct scp_sink_action *act) } } else { - int done = 0; + bool done = false; int i, bufsize; int action; char ch; - act->settime = 0; + act->settime = false; act->buf = NULL; bufsize = 0; while (!done) { - if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + if (!ssh_scp_recv(&ch, 1)) return 1; if (ch == '\n') bump("Protocol error: Unexpected newline"); i = 0; action = ch; do { - if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0) + if (!ssh_scp_recv(&ch, 1)) bump("Lost connection"); if (i >= bufsize) { bufsize = i + 128; @@ -1472,20 +1430,24 @@ int scp_get_sink_action(struct scp_sink_action *act) case '\02': /* fatal error */ bump("%s", act->buf); case 'E': - back->send(backhandle, "", 1); + backend_send(backend, "", 1); act->action = SCP_SINK_ENDDIR; return 0; case 'T': - if (sscanf(act->buf, "%ld %*d %ld %*d", + if (sscanf(act->buf, "%lu %*d %lu %*d", &act->mtime, &act->atime) == 2) { - act->settime = 1; - back->send(backhandle, "", 1); + act->settime = true; + backend_send(backend, "", 1); continue; /* go round again */ } bump("Protocol error: Illegal time format"); case 'C': case 'D': act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR); + if (act->action == SCP_SINK_DIR && !recursive) { + bump("security violation: remote host attempted to create " + "a subdirectory in a non-recursive copy!"); + } break; default: bump("Protocol error: Expected control record"); @@ -1494,7 +1456,7 @@ int scp_get_sink_action(struct scp_sink_action *act) * We will go round this loop only once, unless we hit * `continue' above. */ - done = 1; + done = true; } /* @@ -1502,12 +1464,9 @@ int scp_get_sink_action(struct scp_sink_action *act) * SCP_SINK_DIR. */ { - char sizestr[40]; - - if (sscanf(act->buf, "%lo %39s %n", &act->permissions, - sizestr, &i) != 2) + if (sscanf(act->buf, "%lo %"SCNu64" %n", &act->permissions, + &act->size, &i) != 2) bump("Protocol error: Illegal file descriptor format"); - act->size = uint64_from_decimal(sizestr); act->name = act->buf + i; return 0; } @@ -1530,13 +1489,13 @@ int scp_accept_filexfer(void) errs++; return 1; } - scp_sftp_fileoffset = uint64_make(0, 0); + scp_sftp_fileoffset = 0; scp_sftp_xfer = xfer_download_init(scp_sftp_filehandle, scp_sftp_fileoffset); sfree(scp_sftp_currentname); return 0; } else { - back->send(backhandle, "", 1); + backend_send(backend, "", 1); return 0; /* can't fail */ } } @@ -1578,11 +1537,11 @@ int scp_recv_filedata(char *data, int len) } else actuallen = 0; - scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen); + scp_sftp_fileoffset += actuallen; return actuallen; } else { - return ssh_scp_recv((unsigned char *) data, len); + return ssh_scp_recv(data, len) ? len : 0; } } @@ -1621,7 +1580,7 @@ int scp_finish_filerecv(void) fxp_close_recv(pktin, req); return 0; } else { - back->send(backhandle, "", 1); + backend_send(backend, "", 1); return response(); } } @@ -1640,6 +1599,7 @@ static void run_err(const char *fmt, ...) str2 = dupcat("pscp: ", str, "\n", NULL); sfree(str); scp_send_errmsg(str2); + abandon_stats(); tell_user(stderr, "%s", str2); va_end(ap); sfree(str2); @@ -1650,14 +1610,14 @@ static void run_err(const char *fmt, ...) */ static void source(const char *src) { - uint64 size; + uint64_t size; unsigned long mtime, atime; long permissions; const char *last; RFile *f; int attr; - uint64 i; - uint64 stat_bytes; + uint64_t i; + uint64_t stat_bytes; time_t stat_starttime, stat_lasttime; attr = file_type(src); @@ -1713,40 +1673,33 @@ static void source(const char *src) } if (verbose) { - char sizestr[40]; - uint64_decimal(size, sizestr); - tell_user(stderr, "Sending file %s, size=%s", last, sizestr); + tell_user(stderr, "Sending file %s, size=%"PRIu64, last, size); } if (scp_send_filename(last, size, permissions)) { close_rfile(f); return; } - stat_bytes = uint64_make(0,0); + stat_bytes = 0; stat_starttime = time(NULL); stat_lasttime = 0; #define PSCP_SEND_BLOCK 4096 - for (i = uint64_make(0,0); - uint64_compare(i,size) < 0; - i = uint64_add32(i,PSCP_SEND_BLOCK)) { + for (i = 0; i < size; i += PSCP_SEND_BLOCK) { char transbuf[PSCP_SEND_BLOCK]; int j, k = PSCP_SEND_BLOCK; - if (uint64_compare(uint64_add32(i, k),size) > 0) /* i + k > size */ - k = (uint64_subtract(size, i)).lo; /* k = size - i; */ + if (i + k > size) + k = size - i; if ((j = read_from_file(f, transbuf, k)) != k) { - if (statistics) - printf("\n"); bump("%s: Read error", src); } if (scp_send_filedata(transbuf, k)) bump("%s: Network error occurred", src); if (statistics) { - stat_bytes = uint64_add32(stat_bytes, k); - if (time(NULL) != stat_lasttime || - (uint64_compare(uint64_add32(i, k), size) == 0)) { + stat_bytes += k; + if (time(NULL) != stat_lasttime || i + k == size) { stat_lasttime = time(NULL); print_stats(last, size, stat_bytes, stat_starttime, stat_lasttime); @@ -1809,19 +1762,19 @@ static void rsource(const char *src) static void sink(const char *targ, const char *src) { char *destfname; - int targisdir = 0; - int exists; + bool targisdir = false; + bool exists; int attr; WFile *f; - uint64 received; - int wrerror = 0; - uint64 stat_bytes; + uint64_t received; + bool wrerror = false; + uint64_t stat_bytes; time_t stat_starttime, stat_lasttime; char *stat_name; attr = file_type(targ); if (attr == FILE_TYPE_DIRECTORY) - targisdir = 1; + targisdir = true; if (targetshouldbedirectory && !targisdir) bump("%s: Not a directory", targ); @@ -1871,7 +1824,7 @@ static void sink(const char *targ, const char *src) */ char *striptarget, *stripsrc; - striptarget = stripslashes(act.name, 1); + striptarget = stripslashes(act.name, true); if (striptarget != act.name) { tell_user(stderr, "warning: remote host sent a compound" " pathname '%s'", act.name); @@ -1890,7 +1843,7 @@ static void sink(const char *targ, const char *src) } if (src) { - stripsrc = stripslashes(src, 1); + stripsrc = stripslashes(src, true); if (strcmp(striptarget, stripsrc) && !using_sftp && !scp_unsafe_mode) { tell_user(stderr, "warning: remote host tried to write " @@ -1953,46 +1906,46 @@ static void sink(const char *targ, const char *src) return; } - stat_bytes = uint64_make(0, 0); + stat_bytes = 0; stat_starttime = time(NULL); stat_lasttime = 0; - stat_name = stripslashes(destfname, 1); + stat_name = stripslashes(destfname, true); - received = uint64_make(0, 0); - while (uint64_compare(received,act.size) < 0) { + received = 0; + while (received < act.size) { char transbuf[32768]; - uint64 blksize; + uint64_t blksize; int read; - blksize = uint64_make(0, 32768); - if (uint64_compare(blksize,uint64_subtract(act.size,received)) > 0) - blksize = uint64_subtract(act.size,received); - read = scp_recv_filedata(transbuf, (int)blksize.lo); + blksize = 32768; + if (blksize > act.size - received) + blksize = act.size - received; + read = scp_recv_filedata(transbuf, (int)blksize); if (read <= 0) bump("Lost connection"); if (wrerror) { - received = uint64_add32(received, read); + received += read; continue; } if (write_to_file(f, transbuf, read) != (int)read) { - wrerror = 1; + wrerror = true; /* FIXME: in sftp we can actually abort the transfer */ if (statistics) printf("\r%-25.25s | %50s\n", stat_name, "Write error.. waiting for end of file"); - received = uint64_add32(received, read); + received += read; continue; } if (statistics) { - stat_bytes = uint64_add32(stat_bytes,read); + stat_bytes += read; if (time(NULL) > stat_lasttime || - uint64_compare(uint64_add32(received, read), act.size) == 0) { + received + read == act.size) { stat_lasttime = time(NULL); print_stats(stat_name, act.size, stat_bytes, stat_starttime, stat_lasttime); } } - received = uint64_add32(received, read); + received += read; } if (act.settime) { set_file_times(f, act.mtime, act.atime); @@ -2020,7 +1973,7 @@ static void toremote(int argc, char *argv[]) char *cmd; int i, wc_type; - uploading = 1; + uploading = true; wtarg = argv[argc - 1]; @@ -2052,11 +2005,11 @@ static void toremote(int argc, char *argv[]) if (colon(argv[0]) != NULL) bump("%s: Remote to remote not supported", argv[0]); - wc_type = test_wildcard(argv[0], 1); + wc_type = test_wildcard(argv[0], true); if (wc_type == WCTYPE_NONEXISTENT) bump("%s: No such file or directory\n", argv[0]); else if (wc_type == WCTYPE_WILDCARD) - targetshouldbedirectory = 1; + targetshouldbedirectory = true; } cmd = dupprintf("scp%s%s%s%s -t %s", @@ -2078,7 +2031,7 @@ static void toremote(int argc, char *argv[]) continue; } - wc_type = test_wildcard(src, 1); + wc_type = test_wildcard(src, true); if (wc_type == WCTYPE_NONEXISTENT) { run_err("%s: No such file or directory", src); continue; @@ -2114,7 +2067,7 @@ static void tolocal(int argc, char *argv[]) const char *src, *targ; char *cmd; - uploading = 0; + uploading = false; if (argc != 2) bump("More than one remote source not supported"); @@ -2219,7 +2172,7 @@ static void get_dir_list(int argc, char *argv[]) if (using_sftp) { scp_sftp_listdir(src); } else { - while (ssh_scp_recv((unsigned char *) &c, 1) > 0) + while (ssh_scp_recv(&c, 1)) tell_char(stdout, c); } } @@ -2263,17 +2216,6 @@ static void usage(void) printf(" -sshlog file\n"); printf(" -sshrawlog file\n"); printf(" log protocol details to a file\n"); -#if 0 - /* - * -gui is an internal option, used by GUI front ends to get - * pscp to pass progress reports back to them. It's not an - * ordinary user-accessible option, so it shouldn't be part of - * the command-line help. The only people who need to know - * about it are programmers, and they can read the source. - */ - printf - (" -gui hWnd GUI mode with the windows handle for receiving messages\n"); -#endif cleanup_exit(1); } @@ -2296,8 +2238,8 @@ void cmdline_error(const char *p, ...) exit(1); } -const int share_can_be_downstream = TRUE; -const int share_can_be_upstream = FALSE; +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = false; /* * Main program. (Called `psftp_main' because it gets called from @@ -2309,7 +2251,7 @@ int psftp_main(int argc, char *argv[]) default_protocol = PROT_TELNET; - flags = FLAG_STDERR + flags = 0 #ifdef FLAG_SYNCAGENT | FLAG_SYNCAGENT #endif @@ -2320,7 +2262,7 @@ int psftp_main(int argc, char *argv[]) /* Load Default Settings before doing anything else. */ conf = conf_new(); do_defaults(NULL, conf); - loaded_session = FALSE; + loaded_session = false; for (i = 1; i < argc; i++) { int ret; @@ -2334,16 +2276,16 @@ int psftp_main(int argc, char *argv[]) } else if (ret == 1) { /* We have our own verbosity in addition to `flags'. */ if (flags & FLAG_VERBOSE) - verbose = 1; + verbose = true; } else if (strcmp(argv[i], "-pgpfp") == 0) { pgp_fingerprints(); return 1; } else if (strcmp(argv[i], "-r") == 0) { - recursive = 1; + recursive = true; } else if (strcmp(argv[i], "-p") == 0) { - preserve = 1; + preserve = true; } else if (strcmp(argv[i], "-q") == 0) { - statistics = 0; + statistics = false; } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) { @@ -2352,15 +2294,15 @@ int psftp_main(int argc, char *argv[]) strcmp(argv[i], "--version") == 0) { version(); } else if (strcmp(argv[i], "-ls") == 0) { - list = 1; + list = true; } else if (strcmp(argv[i], "-batch") == 0) { - console_batch_mode = 1; + console_batch_mode = true; } else if (strcmp(argv[i], "-unsafe") == 0) { - scp_unsafe_mode = 1; + scp_unsafe_mode = true; } else if (strcmp(argv[i], "-sftp") == 0) { - try_scp = 0; try_sftp = 1; + try_scp = false; try_sftp = true; } else if (strcmp(argv[i], "-scp") == 0) { - try_scp = 1; try_sftp = 0; + try_scp = true; try_sftp = false; } else if (strcmp(argv[i], "--") == 0) { i++; break; @@ -2370,7 +2312,7 @@ int psftp_main(int argc, char *argv[]) } argc -= i; argv += i; - back = NULL; + backend = NULL; if (list) { if (argc != 1) @@ -2382,7 +2324,7 @@ int psftp_main(int argc, char *argv[]) if (argc < 2) usage(); if (argc > 2) - targetshouldbedirectory = 1; + targetshouldbedirectory = true; if (colon(argv[argc - 1]) != NULL) toremote(argc, argv); @@ -2390,19 +2332,17 @@ int psftp_main(int argc, char *argv[]) tolocal(argc, argv); } - if (back != NULL && back->connected(backhandle)) { + if (backend && backend_connected(backend)) { char ch; - back->special(backhandle, TS_EOF); - sent_eof = TRUE; - ssh_scp_recv((unsigned char *) &ch, 1); + backend_special(backend, SS_EOF, 0); + sent_eof = true; + ssh_scp_recv(&ch, 1); } random_save_seed(); cmdline_cleanup(); - console_provide_logctx(NULL); - back->free(backhandle); - backhandle = NULL; - back = NULL; + backend_free(backend); + backend = NULL; sk_cleanup(); return (errs == 0 ? 0 : 1); } diff --git a/psftp.c b/psftp.c index 5394c1fb..023a16e4 100644 --- a/psftp.c +++ b/psftp.c @@ -14,7 +14,6 @@ #include "storage.h" #include "ssh.h" #include "sftp.h" -#include "int64.h" const char *const appname = "PSFTP"; @@ -27,17 +26,43 @@ const char *const appname = "PSFTP"; static int psftp_connect(char *userhost, char *user, int portnumber); static int do_sftp_init(void); -void do_sftp_cleanup(); +static void do_sftp_cleanup(void); /* ---------------------------------------------------------------------- * sftp client state. */ char *pwd, *homedir; -static Backend *back; -static void *backhandle; -static Conf *conf; -int sent_eof = FALSE; +static Backend *backend; +Conf *conf; +bool sent_eof = false; + +/* ------------------------------------------------------------ + * Seat vtable. + */ + +static int psftp_output(Seat *, bool is_stderr, const void *, int); +static bool psftp_eof(Seat *); + +static const SeatVtable psftp_seat_vt = { + psftp_output, + psftp_eof, + filexfer_get_userpass_input, + nullseat_notify_remote_exit, + console_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + console_verify_ssh_host_key, + console_confirm_weak_crypto_primitive, + console_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + nullseat_get_window_pixel_size, +}; +static Seat psftp_seat[1] = {{ &psftp_seat_vt }}; /* ---------------------------------------------------------------------- * Manage sending requests and waiting for replies. @@ -49,13 +74,17 @@ struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req) sftp_register(req); pktin = sftp_recv(); - if (pktin == NULL) - connection_fatal(NULL, "did not receive SFTP response packet " - "from server"); + if (pktin == NULL) { + seat_connection_fatal( + psftp_seat, "did not receive SFTP response packet from server"); + } rreq = sftp_find_request(pktin); - if (rreq != req) - connection_fatal(NULL, "unable to understand SFTP response packet " - "from server: %s", fxp_error()); + if (rreq != req) { + seat_connection_fatal( + psftp_seat, + "unable to understand SFTP response packet from server: %s", + fxp_error()); + } return pktin; } @@ -198,15 +227,15 @@ static void not_connected(void) /* ---------------------------------------------------------------------- * The meat of the `get' and `put' commands. */ -int sftp_get_file(char *fname, char *outfname, int recurse, int restart) +bool sftp_get_file(char *fname, char *outfname, bool recurse, bool restart) { struct fxp_handle *fh; struct sftp_packet *pktin; struct sftp_request *req; struct fxp_xfer *xfer; - uint64 offset; + uint64_t offset; WFile *file; - int ret, shown_err = FALSE; + bool toret, shown_err = false; struct fxp_attrs attrs; /* @@ -215,7 +244,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) * subsequent FXP_OPEN will return a usable error message.) */ if (recurse) { - int result; + bool result; req = fxp_stat_send(fname); pktin = sftp_wait_for_reply(req); @@ -238,7 +267,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) if (file_type(outfname) != FILE_TYPE_DIRECTORY && !create_directory(outfname)) { printf("%s: Cannot create directory\n", outfname); - return 0; + return false; } /* @@ -252,7 +281,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) if (!dirhandle) { printf("%s: unable to open directory: %s\n", fname, fxp_error()); - return 0; + return false; } nnames = namesize = 0; ournames = NULL; @@ -273,7 +302,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) fxp_close_recv(pktin, req); sfree(ournames); - return 0; + return false; } if (names->nnames == 0) { fxp_free_names(names); @@ -323,12 +352,13 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) if (restart) { while (i < nnames) { char *nextoutfname; - int ret; + bool nonexistent; nextoutfname = dir_file_cat(outfname, ournames[i]->filename); - ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT); + nonexistent = (file_type(nextoutfname) == + FILE_TYPE_NONEXISTENT); sfree(nextoutfname); - if (ret) + if (nonexistent) break; i++; } @@ -344,20 +374,21 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) */ for (; i < nnames; i++) { char *nextfname, *nextoutfname; - int ret; + bool retd; nextfname = dupcat(fname, "/", ournames[i]->filename, NULL); nextoutfname = dir_file_cat(outfname, ournames[i]->filename); - ret = sftp_get_file(nextfname, nextoutfname, recurse, restart); - restart = FALSE; /* after first partial file, do full */ + retd = sftp_get_file( + nextfname, nextoutfname, recurse, restart); + restart = false; /* after first partial file, do full */ sfree(nextoutfname); sfree(nextfname); - if (!ret) { + if (!retd) { for (i = 0; i < nnames; i++) { fxp_free_name(ournames[i]); } sfree(ournames); - return 0; + return false; } } @@ -369,7 +400,7 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) } sfree(ournames); - return 1; + return true; } } @@ -384,13 +415,13 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) if (!fh) { printf("%s: open for read: %s\n", fname, fxp_error()); - return 0; + return false; } if (restart) { file = open_existing_wfile(outfname, NULL); } else { - file = open_new_file(outfname, GET_PERMISSIONS(attrs)); + file = open_new_file(outfname, GET_PERMISSIONS(attrs, -1)); } if (!file) { @@ -400,12 +431,11 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) pktin = sftp_wait_for_reply(req); fxp_close_recv(pktin, req); - return 0; + return false; } if (restart) { - char decbuf[30]; - if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) { + if (seek_file(file, 0, FROM_END) == -1) { close_wfile(file); printf("reget: cannot restart %s - file too large\n", outfname); @@ -413,14 +443,13 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) pktin = sftp_wait_for_reply(req); fxp_close_recv(pktin, req); - return 0; + return false; } offset = get_file_posn(file); - uint64_decimal(offset, decbuf); - printf("reget: restarting at file position %s\n", decbuf); + printf("reget: restarting at file position %"PRIu64"\n", offset); } else { - offset = uint64_make(0, 0); + offset = 0; } printf("remote:%s => local:%s\n", fname, outfname); @@ -429,24 +458,24 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) * FIXME: we can use FXP_FSTAT here to get the file size, and * thus put up a progress bar. */ - ret = 1; + toret = true; xfer = xfer_download_init(fh, offset); while (!xfer_done(xfer)) { void *vbuf; - int len; + int retd, len; int wpos, wlen; xfer_download_queue(xfer); pktin = sftp_recv(); - ret = xfer_download_gotpkt(xfer, pktin); - if (ret <= 0) { + retd = xfer_download_gotpkt(xfer, pktin); + if (retd <= 0) { if (!shown_err) { printf("error while reading: %s\n", fxp_error()); - shown_err = TRUE; + shown_err = true; } - if (ret == INT_MIN) /* pktin not even freed */ + if (retd == INT_MIN) /* pktin not even freed */ sfree(pktin); - ret = 0; + toret = false; } while (xfer_download_data(xfer, &vbuf, &len)) { @@ -457,14 +486,14 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) wlen = write_to_file(file, buf + wpos, len - wpos); if (wlen <= 0) { printf("error while writing local file\n"); - ret = 0; + toret = false; xfer_set_error(xfer); break; } wpos += wlen; } if (wpos < len) { /* we had an error */ - ret = 0; + toret = false; xfer_set_error(xfer); } @@ -480,18 +509,18 @@ int sftp_get_file(char *fname, char *outfname, int recurse, int restart) pktin = sftp_wait_for_reply(req); fxp_close_recv(pktin, req); - return ret; + return toret; } -int sftp_put_file(char *fname, char *outfname, int recurse, int restart) +bool sftp_put_file(char *fname, char *outfname, bool recurse, bool restart) { struct fxp_handle *fh; struct fxp_xfer *xfer; struct sftp_packet *pktin; struct sftp_request *req; - uint64 offset; + uint64_t offset; RFile *file; - int err = 0, eof; + bool err = false, eof; struct fxp_attrs attrs; long permissions; @@ -501,7 +530,7 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) * subsequent fopen will return an error message.) */ if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) { - int result; + bool result; int nnames, namesize; char *name, **ournames; DirHandle *dh; @@ -517,14 +546,14 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) || !(attrs.permissions & 0040000)) { - req = fxp_mkdir_send(outfname); + req = fxp_mkdir_send(outfname, NULL); pktin = sftp_wait_for_reply(req); result = fxp_mkdir_recv(pktin, req); if (!result) { printf("%s: create directory: %s\n", outfname, fxp_error()); - return 0; + return false; } } @@ -537,7 +566,7 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) dh = open_directory(fname); if (!dh) { printf("%s: unable to open directory\n", fname); - return 0; + return false; } while ((name = read_filename(dh)) != NULL) { if (nnames >= namesize) { @@ -590,20 +619,20 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) */ for (; i < nnames; i++) { char *nextfname, *nextoutfname; - int ret; + bool retd; nextfname = dir_file_cat(fname, ournames[i]); nextoutfname = dupcat(outfname, "/", ournames[i], NULL); - ret = sftp_put_file(nextfname, nextoutfname, recurse, restart); - restart = FALSE; /* after first partial file, do full */ + retd = sftp_put_file(nextfname, nextoutfname, recurse, restart); + restart = false; /* after first partial file, do full */ sfree(nextoutfname); sfree(nextfname); - if (!ret) { + if (!retd) { for (i = 0; i < nnames; i++) { sfree(ournames[i]); } sfree(ournames); - return 0; + return false; } } @@ -615,13 +644,13 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) } sfree(ournames); - return 1; + return true; } file = open_existing_file(fname, NULL, NULL, NULL, &permissions); if (!file) { printf("local: unable to open %s\n", fname); - return 0; + return false; } attrs.flags = 0; PUT_PERMISSIONS(attrs, permissions); @@ -638,36 +667,34 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) if (!fh) { close_rfile(file); printf("%s: open for write: %s\n", outfname, fxp_error()); - return 0; + return false; } if (restart) { - char decbuf[30]; struct fxp_attrs attrs; - int ret; + bool retd; req = fxp_fstat_send(fh); pktin = sftp_wait_for_reply(req); - ret = fxp_fstat_recv(pktin, req, &attrs); + retd = fxp_fstat_recv(pktin, req, &attrs); - if (!ret) { + if (!retd) { printf("read size of %s: %s\n", outfname, fxp_error()); - err = 1; + err = true; goto cleanup; } if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { printf("read size of %s: size was not given\n", outfname); - err = 1; + err = true; goto cleanup; } offset = attrs.size; - uint64_decimal(offset, decbuf); - printf("reput: restarting at file position %s\n", decbuf); + printf("reput: restarting at file position %"PRIu64"\n", offset); if (seek_file((WFile *)file, offset, FROM_START) != 0) - seek_file((WFile *)file, uint64_make(0,0), FROM_END); /* *shrug* */ + seek_file((WFile *)file, 0, FROM_END); /* *shrug* */ } else { - offset = uint64_make(0, 0); + offset = 0; } printf("local:%s => remote:%s\n", fname, outfname); @@ -677,7 +704,7 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) * thus put up a progress bar. */ xfer = xfer_upload_init(fh, offset); - eof = 0; + eof = false; while ((!err && !eof) || !xfer_done(xfer)) { char buffer[4096]; int len, ret; @@ -686,9 +713,9 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) len = read_from_file(file, buffer, sizeof(buffer)); if (len == -1) { printf("error while reading local file\n"); - err = 1; + err = true; } else if (len == 0) { - eof = 1; + eof = true; } else { xfer_upload_data(xfer, buffer, len); } @@ -702,7 +729,7 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) sfree(pktin); if (!err) { printf("error while writing: %s\n", fxp_error()); - err = 1; + err = true; } } } @@ -716,13 +743,13 @@ int sftp_put_file(char *fname, char *outfname, int recurse, int restart) if (!fxp_close_recv(pktin, req)) { if (!err) { printf("error while closing: %s", fxp_error()); - err = 1; + err = true; } } close_rfile(file); - return (err == 0) ? 1 : 0; + return !err; } /* ---------------------------------------------------------------------- @@ -743,7 +770,8 @@ SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name) struct sftp_request *req; char *wildcard; char *unwcdir, *tmpdir, *cdir; - int len, check; + int len; + bool check; SftpWildcardMatcher *swcm; struct fxp_handle *dirh; @@ -752,7 +780,7 @@ SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name) * a fully specified directory part, followed by a wildcard * after that. */ - wildcard = stripslashes(name, 0); + wildcard = stripslashes(name, false); unwcdir = dupstr(name); len = wildcard - name; @@ -879,33 +907,34 @@ void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm) * argument and iterate over every matching file. Used in several * PSFTP commands (rmdir, rm, chmod, mv). */ -int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx) +bool wildcard_iterate(char *filename, bool (*func)(void *, char *), void *ctx) { char *unwcfname, *newname, *cname; - int is_wc, ret; + bool is_wc, toret; unwcfname = snewn(strlen(filename)+1, char); is_wc = !wc_unescape(unwcfname, filename); if (is_wc) { SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename); - int matched = FALSE; + bool matched = false; sfree(unwcfname); if (!swcm) - return 0; + return false; - ret = 1; + toret = true; while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) { cname = canonify(newname); if (!cname) { printf("%s: canonify: %s\n", newname, fxp_error()); - ret = 0; + toret = false; } sfree(newname); - matched = TRUE; - ret &= func(ctx, cname); + matched = true; + if (!func(ctx, cname)) + toret = false; sfree(cname); } @@ -919,23 +948,23 @@ int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx) cname = canonify(unwcfname); if (!cname) { printf("%s: canonify: %s\n", filename, fxp_error()); - ret = 0; + toret = false; } - ret = func(ctx, cname); + toret = func(ctx, cname); sfree(cname); sfree(unwcfname); } - return ret; + return toret; } /* * Handy helper function. */ -int is_wildcard(char *name) +bool is_wildcard(char *name) { char *unwcfname = snewn(strlen(name)+1, char); - int is_wc = !wc_unescape(unwcfname, name); + bool is_wc = !wc_unescape(unwcfname, name); sfree(unwcfname); return is_wc; } @@ -967,15 +996,15 @@ int sftp_cmd_quit(struct sftp_command *cmd) int sftp_cmd_close(struct sftp_command *cmd) { - if (back == NULL) { + if (!backend) { not_connected(); return 0; } - if (back != NULL && back->connected(backhandle)) { + if (backend_connected(backend)) { char ch; - back->special(backhandle, TS_EOF); - sent_eof = TRUE; + backend_special(backend, SS_EOF, 0); + sent_eof = true; sftp_recvdata(&ch, 1); } do_sftp_cleanup(); @@ -999,7 +1028,7 @@ int sftp_cmd_ls(struct sftp_command *cmd) struct sftp_request *req; int i; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1015,10 +1044,11 @@ int sftp_cmd_ls(struct sftp_command *cmd) wildcard = NULL; } else { char *tmpdir; - int len, check; + int len; + bool check; sfree(unwcdir); - wildcard = stripslashes(dir, 0); + wildcard = stripslashes(dir, false); unwcdir = dupstr(dir); len = wildcard - dir; unwcdir[len] = '\0'; @@ -1050,6 +1080,8 @@ int sftp_cmd_ls(struct sftp_command *cmd) if (dirh == NULL) { printf("Unable to open %s: %s\n", dir, fxp_error()); + sfree(cdir); + sfree(unwcdir); return 0; } else { nnames = namesize = 0; @@ -1121,19 +1153,19 @@ int sftp_cmd_cd(struct sftp_command *cmd) struct sftp_request *req; char *dir; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } if (cmd->nwords < 2) dir = dupstr(homedir); - else + else { dir = canonify(cmd->words[1]); - - if (!dir) { - printf("%s: canonify: %s\n", dir, fxp_error()); - return 0; + if (!dir) { + printf("%s: canonify: %s\n", cmd->words[1], fxp_error()); + return 0; + } } req = fxp_opendir_send(dir); @@ -1162,7 +1194,7 @@ int sftp_cmd_cd(struct sftp_command *cmd) */ int sftp_cmd_pwd(struct sftp_command *cmd) { - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1180,13 +1212,13 @@ int sftp_cmd_pwd(struct sftp_command *cmd) * transfer (never as a different local name for a remote file) and * can handle wildcards. */ -int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) +int sftp_general_get(struct sftp_command *cmd, bool restart, bool multiple) { char *fname, *unwcfname, *origfname, *origwfname, *outfname; - int i, ret; - int recurse = FALSE; + int i, toret; + bool recurse = false; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1198,7 +1230,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) i++; break; } else if (!strcmp(cmd->words[i], "-r")) { - recurse = TRUE; + recurse = true; } else { printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); return 0; @@ -1211,7 +1243,7 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) return 0; } - ret = 1; + toret = 1; do { SftpWildcardMatcher *swcm; @@ -1251,9 +1283,9 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) if (!multiple && i < cmd->nwords) outfname = cmd->words[i++]; else - outfname = stripslashes(origwfname, 0); + outfname = stripslashes(origwfname, false); - ret = sftp_get_file(fname, outfname, recurse, restart); + toret = sftp_get_file(fname, outfname, recurse, restart); sfree(fname); @@ -1267,24 +1299,24 @@ int sftp_general_get(struct sftp_command *cmd, int restart, int multiple) sfree(unwcfname); if (swcm) sftp_finish_wildcard_matching(swcm); - if (!ret) - return ret; + if (!toret) + return toret; } while (multiple && i < cmd->nwords); - return ret; + return toret; } int sftp_cmd_get(struct sftp_command *cmd) { - return sftp_general_get(cmd, 0, 0); + return sftp_general_get(cmd, false, false); } int sftp_cmd_mget(struct sftp_command *cmd) { - return sftp_general_get(cmd, 0, 1); + return sftp_general_get(cmd, false, true); } int sftp_cmd_reget(struct sftp_command *cmd) { - return sftp_general_get(cmd, 1, 0); + return sftp_general_get(cmd, true, false); } /* @@ -1296,13 +1328,14 @@ int sftp_cmd_reget(struct sftp_command *cmd) * transfer (never as a different remote name for a local file) and * can handle wildcards. */ -int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) +int sftp_general_put(struct sftp_command *cmd, bool restart, bool multiple) { char *fname, *wfname, *origoutfname, *outfname; - int i, ret; - int recurse = FALSE; + int i; + int toret; + bool recurse = false; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1314,7 +1347,7 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) i++; break; } else if (!strcmp(cmd->words[i], "-r")) { - recurse = TRUE; + recurse = true; } else { printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]); return 0; @@ -1327,12 +1360,12 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) return 0; } - ret = 1; + toret = 1; do { WildcardMatcher *wcm; fname = cmd->words[i++]; - if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) { + if (multiple && test_wildcard(fname, false) == WCTYPE_WILDCARD) { wcm = begin_wildcard_matching(fname); wfname = wildcard_get_filename(wcm); if (!wfname) { @@ -1350,7 +1383,7 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) if (!multiple && i < cmd->nwords) origoutfname = cmd->words[i++]; else - origoutfname = stripslashes(wfname, 1); + origoutfname = stripslashes(wfname, true); outfname = canonify(origoutfname); if (!outfname) { @@ -1361,7 +1394,7 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) } return 0; } - ret = sftp_put_file(wfname, outfname, recurse, restart); + toret = sftp_put_file(wfname, outfname, recurse, restart); sfree(outfname); if (wcm) { @@ -1375,24 +1408,24 @@ int sftp_general_put(struct sftp_command *cmd, int restart, int multiple) if (wcm) finish_wildcard_matching(wcm); - if (!ret) - return ret; + if (!toret) + return toret; } while (multiple && i < cmd->nwords); - return ret; + return toret; } int sftp_cmd_put(struct sftp_command *cmd) { - return sftp_general_put(cmd, 0, 0); + return sftp_general_put(cmd, false, false); } int sftp_cmd_mput(struct sftp_command *cmd) { - return sftp_general_put(cmd, 0, 1); + return sftp_general_put(cmd, false, true); } int sftp_cmd_reput(struct sftp_command *cmd) { - return sftp_general_put(cmd, 1, 0); + return sftp_general_put(cmd, true, false); } int sftp_cmd_mkdir(struct sftp_command *cmd) @@ -1400,10 +1433,10 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) char *dir; struct sftp_packet *pktin; struct sftp_request *req; - int result; + bool result; int i, ret; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1417,11 +1450,11 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) for (i = 1; i < cmd->nwords; i++) { dir = canonify(cmd->words[i]); if (!dir) { - printf("%s: canonify: %s\n", dir, fxp_error()); + printf("%s: canonify: %s\n", cmd->words[i], fxp_error()); return 0; } - req = fxp_mkdir_send(dir); + req = fxp_mkdir_send(dir, NULL); pktin = sftp_wait_for_reply(req); result = fxp_mkdir_recv(pktin, req); @@ -1437,11 +1470,11 @@ int sftp_cmd_mkdir(struct sftp_command *cmd) return ret; } -static int sftp_action_rmdir(void *vctx, char *dir) +static bool sftp_action_rmdir(void *vctx, char *dir) { struct sftp_packet *pktin; struct sftp_request *req; - int result; + bool result; req = fxp_rmdir_send(dir); pktin = sftp_wait_for_reply(req); @@ -1449,19 +1482,19 @@ static int sftp_action_rmdir(void *vctx, char *dir) if (!result) { printf("rmdir %s: %s\n", dir, fxp_error()); - return 0; + return false; } printf("rmdir %s: OK\n", dir); - return 1; + return true; } int sftp_cmd_rmdir(struct sftp_command *cmd) { int i, ret; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1478,11 +1511,11 @@ int sftp_cmd_rmdir(struct sftp_command *cmd) return ret; } -static int sftp_action_rm(void *vctx, char *fname) +static bool sftp_action_rm(void *vctx, char *fname) { struct sftp_packet *pktin; struct sftp_request *req; - int result; + bool result; req = fxp_remove_send(fname); pktin = sftp_wait_for_reply(req); @@ -1490,19 +1523,19 @@ static int sftp_action_rm(void *vctx, char *fname) if (!result) { printf("rm %s: %s\n", fname, fxp_error()); - return 0; + return false; } printf("rm %s: OK\n", fname); - return 1; + return true; } int sftp_cmd_rm(struct sftp_command *cmd) { int i, ret; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1519,12 +1552,12 @@ int sftp_cmd_rm(struct sftp_command *cmd) return ret; } -static int check_is_dir(char *dstfname) +static bool check_is_dir(char *dstfname) { struct sftp_packet *pktin; struct sftp_request *req; struct fxp_attrs attrs; - int result; + bool result; req = fxp_stat_send(dstfname); pktin = sftp_wait_for_reply(req); @@ -1533,24 +1566,24 @@ static int check_is_dir(char *dstfname) if (result && (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && (attrs.permissions & 0040000)) - return TRUE; + return true; else - return FALSE; + return false; } struct sftp_context_mv { char *dstfname; - int dest_is_dir; + bool dest_is_dir; }; -static int sftp_action_mv(void *vctx, char *srcfname) +static bool sftp_action_mv(void *vctx, char *srcfname) { struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx; struct sftp_packet *pktin; struct sftp_request *req; const char *error; char *finalfname, *newcanon = NULL; - int ret, result; + bool toret, result; if (ctx->dest_is_dir) { char *p; @@ -1563,7 +1596,7 @@ static int sftp_action_mv(void *vctx, char *srcfname) if (!newcanon) { printf("%s: canonify: %s\n", newname, fxp_error()); sfree(newname); - return 0; + return false; } sfree(newname); @@ -1580,14 +1613,14 @@ static int sftp_action_mv(void *vctx, char *srcfname) if (error) { printf("mv %s %s: %s\n", srcfname, finalfname, error); - ret = 0; + toret = false; } else { printf("%s -> %s\n", srcfname, finalfname); - ret = 1; + toret = true; } sfree(newcanon); - return ret; + return toret; } int sftp_cmd_mv(struct sftp_command *cmd) @@ -1595,7 +1628,7 @@ int sftp_cmd_mv(struct sftp_command *cmd) struct sftp_context_mv actx, *ctx = &actx; int i, ret; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1639,12 +1672,12 @@ struct sftp_context_chmod { unsigned attrs_clr, attrs_xor; }; -static int sftp_action_chmod(void *vctx, char *fname) +static bool sftp_action_chmod(void *vctx, char *fname) { struct fxp_attrs attrs; struct sftp_packet *pktin; struct sftp_request *req; - int result; + bool result; unsigned oldperms, newperms; struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx; @@ -1655,7 +1688,7 @@ static int sftp_action_chmod(void *vctx, char *fname) if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { printf("get attrs for %s: %s\n", fname, result ? "file permissions not provided" : fxp_error()); - return 0; + return false; } attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */ @@ -1665,7 +1698,7 @@ static int sftp_action_chmod(void *vctx, char *fname) newperms = attrs.permissions & 07777; if (oldperms == newperms) - return 1; /* no need to do anything! */ + return true; /* no need to do anything! */ req = fxp_setstat_send(fname, attrs); pktin = sftp_wait_for_reply(req); @@ -1673,12 +1706,12 @@ static int sftp_action_chmod(void *vctx, char *fname) if (!result) { printf("set attrs for %s: %s\n", fname, fxp_error()); - return 0; + return false; } printf("%s: %04o -> %04o\n", fname, oldperms, newperms); - return 1; + return true; } int sftp_cmd_chmod(struct sftp_command *cmd) @@ -1687,7 +1720,7 @@ int sftp_cmd_chmod(struct sftp_command *cmd) int i, ret; struct sftp_context_chmod actx, *ctx = &actx; - if (back == NULL) { + if (!backend) { not_connected(); return 0; } @@ -1813,7 +1846,7 @@ static int sftp_cmd_open(struct sftp_command *cmd) { int portnumber; - if (back != NULL) { + if (backend) { printf("psftp: already connected\n"); return 0; } @@ -1833,7 +1866,7 @@ static int sftp_cmd_open(struct sftp_command *cmd) portnumber = 0; if (psftp_connect(cmd->words[1], NULL, portnumber)) { - back = NULL; /* connection is already closed */ + backend = NULL; /* connection is already closed */ return -1; /* this is fatal */ } do_sftp_init(); @@ -1898,7 +1931,7 @@ static struct sftp_cmd_lookup { * `shorthelp' is the name of a primary command, which * contains the help that should double up for this command. */ - int listed; /* do we list this in primary help? */ + bool listed; /* do we list this in primary help? */ const char *shorthelp; const char *longhelp; int (*obey) (struct sftp_command *); @@ -1908,20 +1941,20 @@ static struct sftp_cmd_lookup { * in ASCII order. */ { - "!", TRUE, "run a local command", + "!", true, "run a local command", "\n" /* FIXME: this example is crap for non-Windows. */ " Runs a local command. For example, \"!del myfile\".\n", sftp_cmd_pling }, { - "bye", TRUE, "finish your SFTP session", + "bye", true, "finish your SFTP session", "\n" " Terminates your SFTP session and quits the PSFTP program.\n", sftp_cmd_quit }, { - "cd", TRUE, "change your remote working directory", + "cd", true, "change your remote working directory", " [ ]\n" " Change the remote working directory for your SFTP session.\n" " If a new working directory is not supplied, you will be\n" @@ -1929,7 +1962,7 @@ static struct sftp_cmd_lookup { sftp_cmd_cd }, { - "chmod", TRUE, "change file permissions and modes", + "chmod", true, "change file permissions and modes", " [ ... ]\n" " Change the file permissions on one or more remote files or\n" " directories.\n" @@ -1957,7 +1990,7 @@ static struct sftp_cmd_lookup { sftp_cmd_chmod }, { - "close", TRUE, "finish your SFTP session but do not quit PSFTP", + "close", true, "finish your SFTP session but do not quit PSFTP", "\n" " Terminates your SFTP session, but does not quit the PSFTP\n" " program. You can then use \"open\" to start another SFTP\n" @@ -1965,16 +1998,16 @@ static struct sftp_cmd_lookup { sftp_cmd_close }, { - "del", TRUE, "delete files on the remote server", + "del", true, "delete files on the remote server", " [ ... ]\n" " Delete a file or files from the server.\n", sftp_cmd_rm }, { - "delete", FALSE, "del", NULL, sftp_cmd_rm + "delete", false, "del", NULL, sftp_cmd_rm }, { - "dir", TRUE, "list remote files", + "dir", true, "list remote files", " [ ]/[ ]\n" " List the contents of a specified directory on the server.\n" " If is not given, the current working directory\n" @@ -1984,10 +2017,10 @@ static struct sftp_cmd_lookup { sftp_cmd_ls }, { - "exit", TRUE, "bye", NULL, sftp_cmd_quit + "exit", true, "bye", NULL, sftp_cmd_quit }, { - "get", TRUE, "download a file from the server to your local machine", + "get", true, "download a file from the server to your local machine", " [ -r ] [ -- ] [ ]\n" " Downloads a file on the server and stores it locally under\n" " the same name, or under a different one if you supply the\n" @@ -1996,7 +2029,7 @@ static struct sftp_cmd_lookup { sftp_cmd_get }, { - "help", TRUE, "give help", + "help", true, "give help", " [ [ ... ] ]\n" " Give general help if no commands are specified.\n" " If one or more commands are specified, give specific help on\n" @@ -2004,25 +2037,25 @@ static struct sftp_cmd_lookup { sftp_cmd_help }, { - "lcd", TRUE, "change local working directory", + "lcd", true, "change local working directory", " \n" " Change the local working directory of the PSFTP program (the\n" " default location where the \"get\" command will save files).\n", sftp_cmd_lcd }, { - "lpwd", TRUE, "print local working directory", + "lpwd", true, "print local working directory", "\n" " Print the local working directory of the PSFTP program (the\n" " default location where the \"get\" command will save files).\n", sftp_cmd_lpwd }, { - "ls", TRUE, "dir", NULL, + "ls", true, "dir", NULL, sftp_cmd_ls }, { - "mget", TRUE, "download multiple files at once", + "mget", true, "download multiple files at once", " [ -r ] [ -- ] [ ... ]\n" " Downloads many files from the server, storing each one under\n" " the same name it has on the server side. You can use wildcards\n" @@ -2031,13 +2064,13 @@ static struct sftp_cmd_lookup { sftp_cmd_mget }, { - "mkdir", TRUE, "create directories on the remote server", + "mkdir", true, "create directories on the remote server", " [ ... ]\n" " Creates directories with the given names on the server.\n", sftp_cmd_mkdir }, { - "mput", TRUE, "upload multiple files at once", + "mput", true, "upload multiple files at once", " [ -r ] [ -- ] [ ... ]\n" " Uploads many files to the server, storing each one under the\n" " same name it has on the client side. You can use wildcards\n" @@ -2046,7 +2079,7 @@ static struct sftp_cmd_lookup { sftp_cmd_mput }, { - "mv", TRUE, "move or rename file(s) on the remote server", + "mv", true, "move or rename file(s) on the remote server", " [ ... ] \n" " Moves or renames (s) on the server to ,\n" " also on the server.\n" @@ -2058,14 +2091,14 @@ static struct sftp_cmd_lookup { sftp_cmd_mv }, { - "open", TRUE, "connect to a host", + "open", true, "connect to a host", " [@] []\n" " Establishes an SFTP connection to a given host. Only usable\n" " when you are not already connected to a server.\n", sftp_cmd_open }, { - "put", TRUE, "upload a file from your local machine to the server", + "put", true, "upload a file from your local machine to the server", " [ -r ] [ -- ] [ ]\n" " Uploads a file to the server and stores it there under\n" " the same name, or under a different one if you supply the\n" @@ -2074,17 +2107,17 @@ static struct sftp_cmd_lookup { sftp_cmd_put }, { - "pwd", TRUE, "print your remote working directory", + "pwd", true, "print your remote working directory", "\n" " Print the current remote working directory for your SFTP session.\n", sftp_cmd_pwd }, { - "quit", TRUE, "bye", NULL, + "quit", true, "bye", NULL, sftp_cmd_quit }, { - "reget", TRUE, "continue downloading files", + "reget", true, "continue downloading files", " [ -r ] [ -- ] [ ]\n" " Works exactly like the \"get\" command, but the local file\n" " must already exist. The download will begin at the end of the\n" @@ -2093,15 +2126,15 @@ static struct sftp_cmd_lookup { sftp_cmd_reget }, { - "ren", TRUE, "mv", NULL, + "ren", true, "mv", NULL, sftp_cmd_mv }, { - "rename", FALSE, "mv", NULL, + "rename", false, "mv", NULL, sftp_cmd_mv }, { - "reput", TRUE, "continue uploading files", + "reput", true, "continue uploading files", " [ -r ] [ -- ] [ ]\n" " Works exactly like the \"put\" command, but the remote file\n" " must already exist. The upload will begin at the end of the\n" @@ -2110,11 +2143,11 @@ static struct sftp_cmd_lookup { sftp_cmd_reput }, { - "rm", TRUE, "del", NULL, + "rm", true, "del", NULL, sftp_cmd_rm }, { - "rmdir", TRUE, "remove directories on the remote server", + "rmdir", true, "remove directories on the remote server", " [ ... ]\n" " Removes the directory with the given name on the server.\n" " The directory will not be removed unless it is empty.\n" @@ -2198,7 +2231,7 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) char *line; struct sftp_command *cmd; char *p, *q, *r; - int quoting; + bool quoting; cmd = snew(struct sftp_command); cmd->words = NULL; @@ -2212,7 +2245,7 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) printf("psftp> "); line = fgetline(fp); } else { - line = ssh_sftp_get_cmdline("psftp> ", back == NULL); + line = ssh_sftp_get_cmdline("psftp> ", !backend); } if (!line || !*line) { @@ -2275,7 +2308,7 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) break; /* mark start of word */ q = r = p; /* q sits at start, r writes word */ - quoting = 0; + quoting = false; while (*p) { if (!quoting && (*p == ' ' || *p == '\t')) break; /* reached end of word */ @@ -2350,17 +2383,16 @@ static int do_sftp_init(void) return 0; } -void do_sftp_cleanup() +static void do_sftp_cleanup(void) { char ch; - if (back) { - back->special(backhandle, TS_EOF); - sent_eof = TRUE; + if (backend) { + backend_special(backend, SS_EOF, 0); + sent_eof = true; sftp_recvdata(&ch, 1); - back->free(backhandle); + backend_free(backend); sftp_cleanup_request(); - back = NULL; - backhandle = NULL; + backend = NULL; } if (pwd) { sfree(pwd); @@ -2435,67 +2467,9 @@ int do_sftp(int mode, int modeflags, char *batchfile) * Dirty bits: integration with PuTTY. */ -static int verbose = 0; - -/* - * Print an error message and perform a fatal exit. - */ -void fatalbox(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - fputs(str2, stderr); - sfree(str2); +static bool verbose = false; - cleanup_exit(1); -} -void modalfatalbox(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - fputs(str2, stderr); - sfree(str2); - - cleanup_exit(1); -} -void nonfatal(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Error: ", str, "\n", NULL); - sfree(str); - va_end(ap); - fputs(str2, stderr); - sfree(str2); -} -void connection_fatal(void *frontend, const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - str2 = dupcat("Fatal: ", str, "\n", NULL); - sfree(str); - va_end(ap); - fputs(str2, stderr); - sfree(str2); - - cleanup_exit(1); -} - -void ldisc_echoedit_update(void *handle) { } +void ldisc_echoedit_update(Ldisc *ldisc) { } /* * In psftp, all agent requests should be synchronous, so this is a @@ -2512,7 +2486,7 @@ void agent_schedule_callback(void (*callback)(void *, void *, int), * is available. * * To do this, we repeatedly call the SSH protocol module, with our - * own trap in from_backend() to catch the data that comes back. We + * own psftp_output() function to catch the data that comes back. We * do this until we have enough data. */ @@ -2520,7 +2494,8 @@ static unsigned char *outptr; /* where to put the data */ static unsigned outlen; /* how much data required */ static unsigned char *pending = NULL; /* any spare data */ static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */ -int from_backend(void *frontend, int is_stderr, const char *data, int datalen) +static int psftp_output(Seat *seat, bool is_stderr, + const void *data, int datalen) { unsigned char *p = (unsigned char *) data; unsigned len = (unsigned) datalen; @@ -2564,16 +2539,8 @@ int from_backend(void *frontend, int is_stderr, const char *data, int datalen) return 0; } -int from_backend_untrusted(void *frontend_handle, const char *data, int len) -{ - /* - * No "untrusted" output should get here (the way the code is - * currently, it's all diverted by FLAG_STDERR). - */ - assert(!"Unexpected call to from_backend_untrusted()"); - return 0; /* not reached */ -} -int from_backend_eof(void *frontend) + +static bool psftp_eof(Seat *seat) { /* * We expect to be the party deciding when to close the @@ -2581,12 +2548,13 @@ int from_backend_eof(void *frontend) * should panic. */ if (!sent_eof) { - connection_fatal(frontend, - "Received unexpected end-of-file from SFTP server"); + seat_connection_fatal( + psftp_seat, "Received unexpected end-of-file from SFTP server"); } - return FALSE; + return false; } -int sftp_recvdata(char *buf, int len) + +bool sftp_recvdata(char *buf, int len) { outptr = (unsigned char *) buf; outlen = len; @@ -2610,24 +2578,24 @@ int sftp_recvdata(char *buf, int len) pending = NULL; } if (outlen == 0) - return 1; + return true; } while (outlen > 0) { - if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0) - return 0; /* doom */ + if (backend_exitcode(backend) >= 0 || ssh_sftp_loop_iteration() < 0) + return false; /* doom */ } - return 1; + return true; } -int sftp_senddata(char *buf, int len) +bool sftp_senddata(char *buf, int len) { - back->send(backhandle, buf, len); - return 1; + backend_send(backend, buf, len); + return true; } int sftp_sendbuffer(void) { - return back->sendbuffer(backhandle); + return backend_sendbuffer(backend); } /* @@ -2681,7 +2649,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber) { char *host, *realhost; const char *err; - void *logctx; + LogContext *logctx; /* Separate host and username */ host = userhost; @@ -2799,9 +2767,9 @@ static int psftp_connect(char *userhost, char *user, int portnumber) * things like SCP and SFTP: agent forwarding, port forwarding, * X forwarding. */ - conf_set_int(conf, CONF_x11_forward, 0); - conf_set_int(conf, CONF_agentfwd, 0); - conf_set_int(conf, CONF_ssh_simple, TRUE); + conf_set_bool(conf, CONF_x11_forward, false); + conf_set_bool(conf, CONF_agentfwd, false); + conf_set_bool(conf, CONF_ssh_simple, true); { char *key; while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL) @@ -2810,8 +2778,8 @@ static int psftp_connect(char *userhost, char *user, int portnumber) /* Set up subsystem name. */ conf_set_str(conf, CONF_remote_cmd, "sftp"); - conf_set_int(conf, CONF_ssh_subsys, TRUE); - conf_set_int(conf, CONF_nopty, TRUE); + conf_set_bool(conf, CONF_ssh_subsys, true); + conf_set_bool(conf, CONF_nopty, true); /* * Set up fallback option, for SSH-1 servers or servers with the @@ -2836,27 +2804,23 @@ static int psftp_connect(char *userhost, char *user, int portnumber) "test -x /usr/local/lib/sftp-server &&" " exec /usr/local/lib/sftp-server\n" "exec sftp-server"); - conf_set_int(conf, CONF_ssh_subsys2, FALSE); - - back = &ssh_backend; + conf_set_bool(conf, CONF_ssh_subsys2, false); - logctx = log_init(NULL, conf); - console_provide_logctx(logctx); + logctx = log_init(default_logpolicy, conf); platform_psftp_pre_conn_setup(); - err = back->init(NULL, &backhandle, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, 0, - conf_get_int(conf, CONF_tcp_keepalives)); + err = backend_init(&ssh_backend, psftp_seat, &backend, logctx, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, 0, + conf_get_bool(conf, CONF_tcp_keepalives)); if (err != NULL) { fprintf(stderr, "ssh_init: %s\n", err); return 1; } - back->provide_logctx(backhandle, logctx); - while (!back->sendok(backhandle)) { - if (back->exitcode(backhandle) >= 0) + while (!backend_sendok(backend)) { + if (backend_exitcode(backend) >= 0) return 1; if (ssh_sftp_loop_iteration() < 0) { fprintf(stderr, "ssh_init: error during SSH connection setup\n"); @@ -2881,8 +2845,8 @@ void cmdline_error(const char *p, ...) exit(1); } -const int share_can_be_downstream = TRUE; -const int share_can_be_upstream = FALSE; +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = false; /* * Main program. Parse arguments etc. @@ -2896,7 +2860,7 @@ int psftp_main(int argc, char *argv[]) int modeflags = 0; char *batchfile = NULL; - flags = FLAG_STDERR | FLAG_INTERACTIVE + flags = FLAG_INTERACTIVE #ifdef FLAG_SYNCAGENT | FLAG_SYNCAGENT #endif @@ -2909,7 +2873,7 @@ int psftp_main(int argc, char *argv[]) /* Load Default Settings before doing anything else. */ conf = conf_new(); do_defaults(NULL, conf); - loaded_session = FALSE; + loaded_session = false; for (i = 1; i < argc; i++) { int ret; @@ -2928,7 +2892,7 @@ int psftp_main(int argc, char *argv[]) } else if (ret == 1) { /* We have our own verbosity in addition to `flags'. */ if (flags & FLAG_VERBOSE) - verbose = 1; + verbose = true; } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) { @@ -2940,7 +2904,7 @@ int psftp_main(int argc, char *argv[]) strcmp(argv[i], "--version") == 0) { version(); } else if (strcmp(argv[i], "-batch") == 0) { - console_batch_mode = 1; + console_batch_mode = true; } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { mode = 1; batchfile = argv[++i]; @@ -2957,7 +2921,7 @@ int psftp_main(int argc, char *argv[]) } argc -= i; argv += i; - back = NULL; + backend = NULL; /* * If the loaded session provides a hostname, and a hostname has not @@ -2987,16 +2951,15 @@ int psftp_main(int argc, char *argv[]) ret = do_sftp(mode, modeflags, batchfile); - if (back != NULL && back->connected(backhandle)) { + if (backend && backend_connected(backend)) { char ch; - back->special(backhandle, TS_EOF); - sent_eof = TRUE; + backend_special(backend, SS_EOF, 0); + sent_eof = true; sftp_recvdata(&ch, 1); } do_sftp_cleanup(); random_save_seed(); cmdline_cleanup(); - console_provide_logctx(NULL); sk_cleanup(); return ret; diff --git a/psftp.h b/psftp.h index 36965489..26a9d23b 100644 --- a/psftp.h +++ b/psftp.h @@ -3,8 +3,6 @@ * platform-specific SFTP module. */ -#include "int64.h" - #ifndef PUTTY_PSFTP_H #define PUTTY_PSFTP_H @@ -38,14 +36,14 @@ int ssh_sftp_loop_iteration(void); * Read a command line for PSFTP from standard input. Caller must * free. * - * If `backend_required' is TRUE, should also listen for activity + * If `backend_required' is true, should also listen for activity * at the backend (rekeys, clientalives, unexpected closures etc) * and respond as necessary, and if the backend closes it should * treat this as a failure condition. If `backend_required' is - * FALSE, a back end is not (intentionally) active at all (e.g. + * false, a back end is not (intentionally) active at all (e.g. * psftp before an `open' command). */ -char *ssh_sftp_get_cmdline(const char *prompt, int backend_required); +char *ssh_sftp_get_cmdline(const char *prompt, bool backend_required); /* * Platform-specific function called when we're about to make a @@ -93,10 +91,10 @@ typedef struct RFile RFile; typedef struct WFile WFile; /* Output params size, perms, mtime and atime can all be NULL if * desired. perms will be -1 if the OS does not support POSIX permissions. */ -RFile *open_existing_file(const char *name, uint64 *size, +RFile *open_existing_file(const char *name, uint64_t *size, unsigned long *mtime, unsigned long *atime, long *perms); -WFile *open_existing_wfile(const char *name, uint64 *size); +WFile *open_existing_wfile(const char *name, uint64_t *size); /* Returns <0 on error, 0 on eof, or number of bytes read, as usual */ int read_from_file(RFile *f, void *buffer, int length); /* Closes and frees the RFile */ @@ -109,9 +107,9 @@ void set_file_times(WFile *f, unsigned long mtime, unsigned long atime); void close_wfile(WFile *f); /* Seek offset bytes through file */ enum { FROM_START, FROM_CURRENT, FROM_END }; -int seek_file(WFile *f, uint64 offset, int whence); +int seek_file(WFile *f, uint64_t offset, int whence); /* Get file position */ -uint64 get_file_posn(WFile *f); +uint64_t get_file_posn(WFile *f); /* * Determine the type of a file: nonexistent, file, directory or * weird. `weird' covers anything else - named pipes, Unix sockets, @@ -151,7 +149,7 @@ void close_directory(DirHandle *dir); enum { WCTYPE_NONEXISTENT, WCTYPE_FILENAME, WCTYPE_WILDCARD }; -int test_wildcard(const char *name, int cmdline); +int test_wildcard(const char *name, bool cmdline); /* * Actually return matching file names for a local wildcard. @@ -168,14 +166,14 @@ void finish_wildcard_matching(WildcardMatcher *dir); * to filenames returned from FXP_READDIR, which means we can panic * if we see _anything_ resembling a directory separator. * - * Returns TRUE if the filename is kosher, FALSE if dangerous. + * Returns true if the filename is kosher, false if dangerous. */ -int vet_filename(const char *name); +bool vet_filename(const char *name); /* - * Create a directory. Returns 0 on error, !=0 on success. + * Create a directory. Returns true on success, false on error. */ -int create_directory(const char *name); +bool create_directory(const char *name); /* * Concatenate a directory name and a file name. The way this is @@ -198,6 +196,6 @@ char *dir_file_cat(const char *dir, const char *file); * pair of overloaded functions, one mapping mutable->mutable and the * other const->const :-( */ -char *stripslashes(const char *str, int local); +char *stripslashes(const char *str, bool local); #endif /* PUTTY_PSFTP_H */ diff --git a/putty.h b/putty.h index fd2d0250..0f4ab6db 100644 --- a/putty.h +++ b/putty.h @@ -2,6 +2,7 @@ #define PUTTY_PUTTY_H #include /* for wchar_t */ +#include /* for INT_MAX */ /* * Global variables. Most modules declare these `extern', but @@ -16,27 +17,31 @@ #endif #endif -#ifndef DONE_TYPEDEFS -#define DONE_TYPEDEFS -typedef struct conf_tag Conf; -typedef struct backend_tag Backend; -typedef struct terminal_tag Terminal; -#endif - +#include "defs.h" #include "puttyps.h" #include "network.h" #include "misc.h" +#include "marshal.h" + +/* + * We express various time intervals in unsigned long minutes, but may need to + * clip some values so that the resulting number of ticks does not overflow an + * integer value. + */ +#define MAX_TICK_MINS (INT_MAX / (60 * TICKSPERSEC)) /* - * Fingerprints of the PGP master keys that can be used to establish a trust - * path between an executable and other files. + * Fingerprints of the current and previous PGP master keys, to + * establish a trust path between an executable and other files. */ -#define PGP_MASTER_KEY_FP \ +#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_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit" +#define PGP_PREV_MASTER_KEY_FP \ "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C" -#define PGP_RSA_MASTER_KEY_FP \ - "8F 15 97 DA 25 30 AB 0D 88 D1 92 54 11 CF 0C 4C" -#define PGP_DSA_MASTER_KEY_FP \ - "313C 3E76 4B74 C2C5 F2AE 83A8 4F5E 6DF5 6A93 B34E" /* Three attribute types: * The ATTRs (normal attributes) are stored with the characters in @@ -104,15 +109,16 @@ typedef struct terminal_tag Terminal; */ #define UCSWIDE 0xDFFF -#define ATTR_NARROW 0x800000U -#define ATTR_WIDE 0x400000U -#define ATTR_BOLD 0x040000U -#define ATTR_UNDER 0x080000U -#define ATTR_REVERSE 0x100000U -#define ATTR_BLINK 0x200000U -#define ATTR_FGMASK 0x0001FFU -#define ATTR_BGMASK 0x03FE00U -#define ATTR_COLOURS 0x03FFFFU +#define ATTR_NARROW 0x0800000U +#define ATTR_WIDE 0x0400000U +#define ATTR_BOLD 0x0040000U +#define ATTR_UNDER 0x0080000U +#define ATTR_REVERSE 0x0100000U +#define ATTR_BLINK 0x0200000U +#define ATTR_FGMASK 0x00001FFU +#define ATTR_BGMASK 0x003FE00U +#define ATTR_COLOURS 0x003FFFFU +#define ATTR_DIM 0x1000000U #define ATTR_FGSHIFT 0 #define ATTR_BGSHIFT 9 @@ -147,7 +153,7 @@ struct sesslist { struct unicode_data { char **uni_tbl; - int dbcs_screenfont; + bool dbcs_screenfont; int font_codepage; int line_codepage; wchar_t unitab_scoacs[256]; @@ -167,36 +173,79 @@ struct unicode_data { #define LGTYP_PACKETS 3 /* logmode: SSH data packets */ #define LGTYP_SSHRAW 4 /* logmode: SSH raw data */ +/* + * Enumeration of 'special commands' that can be sent during a + * session, separately from the byte stream of ordinary session data. + */ typedef enum { - /* Actual special commands. Originally Telnet, but some codes have - * been re-used for similar specials in other protocols. */ - TS_AYT, TS_BRK, TS_SYNCH, TS_EC, TS_EL, TS_GA, TS_NOP, TS_ABORT, - TS_AO, TS_IP, TS_SUSP, TS_EOR, TS_EOF, TS_LECHO, TS_RECHO, TS_PING, - TS_EOL, - /* Special command for SSH. */ - TS_REKEY, - /* POSIX-style signals. (not Telnet) */ - TS_SIGABRT, TS_SIGALRM, TS_SIGFPE, TS_SIGHUP, TS_SIGILL, - TS_SIGINT, TS_SIGKILL, TS_SIGPIPE, TS_SIGQUIT, TS_SIGSEGV, - TS_SIGTERM, TS_SIGUSR1, TS_SIGUSR2, - /* Pseudo-specials used for constructing the specials menu. */ - TS_SEP, /* Separator */ - TS_SUBMENU, /* Start a new submenu with specified name */ - TS_EXITMENU, /* Exit current submenu or end of specials */ - /* Starting point for protocols to invent special-action codes - * that can't live in this enum at all, e.g. because they change - * with every session. + /* + * Commands that are generally useful in multiple backends. + */ + SS_BRK, /* serial-line break */ + SS_EOF, /* end-of-file on session input */ + SS_NOP, /* transmit data with no effect */ + SS_PING, /* try to keep the session alive (probably, but not + * necessarily, implemented as SS_NOP) */ + + /* + * Commands specific to Telnet. + */ + SS_AYT, /* Are You There */ + SS_SYNCH, /* Synch */ + SS_EC, /* Erase Character */ + SS_EL, /* Erase Line */ + SS_GA, /* Go Ahead */ + SS_ABORT, /* Abort Process */ + SS_AO, /* Abort Output */ + SS_IP, /* Interrupt Process */ + SS_SUSP, /* Suspend Process */ + SS_EOR, /* End Of Record */ + SS_EOL, /* Telnet end-of-line sequence (CRLF, as opposed to CR + * NUL that escapes a literal CR) */ + + /* + * Commands specific to SSH. + */ + SS_REKEY, /* trigger an immediate repeat key exchange */ + SS_XCERT, /* cross-certify another host key ('arg' indicates which) */ + + /* + * Send a POSIX-style signal. (Useful in SSH and also pterm.) * - * Of course, this must remain the last value in this - * enumeration. */ - TS_LOCALSTART -} Telnet_Special; + * We use the master list in sshsignals.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" + #undef SIGNAL_MAIN + #undef SIGNAL_SUB -struct telnet_special { + /* + * These aren't really special commands, but they appear in the + * enumeration because the list returned from + * backend_get_specials() will use them to specify the structure + * of the GUI specials menu. + */ + SS_SEP, /* Separator */ + SS_SUBMENU, /* Start a new submenu with specified name */ + SS_EXITMENU, /* Exit current submenu, or end of entire specials list */ +} SessionSpecialCode; + +/* + * The structure type returned from backend_get_specials. + */ +struct SessionSpecial { const char *name; - int code; + SessionSpecialCode code; + int arg; }; +/* Needed by both sshchan.h and sshppl.h */ +typedef void (*add_special_fn_t)( + void *ctx, const char *text, SessionSpecialCode code, int arg); + typedef enum { MBT_NOTHING, MBT_LEFT, MBT_MIDDLE, MBT_RIGHT, /* `raw' button designations */ @@ -318,7 +367,8 @@ enum { * Line discipline options which the backend might try to control. */ LD_EDIT, /* local line editing */ - LD_ECHO /* local echo */ + LD_ECHO, /* local echo */ + LD_N_OPTIONS }; enum { @@ -433,46 +483,72 @@ enum { * host name has already been resolved or will be resolved at * the proxy end. */ - ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME + ADDRTYPE_UNSPEC, + ADDRTYPE_IPV4, + ADDRTYPE_IPV6, + ADDRTYPE_LOCAL, /* e.g. Unix domain socket, or Windows named pipe */ + ADDRTYPE_NAME /* SockAddr storing an unresolved host name */ }; -struct backend_tag { - const char *(*init) (void *frontend_handle, void **backend_handle, - Conf *conf, const char *host, int port, - char **realhost, int nodelay, int keepalive); - void (*free) (void *handle); - /* back->reconfig() passes in a replacement configuration. */ - void (*reconfig) (void *handle, Conf *conf); - /* back->send() returns the current amount of buffered data. */ - int (*send) (void *handle, const char *buf, int len); - /* back->sendbuffer() does the same thing but without attempting a send */ - int (*sendbuffer) (void *handle); - void (*size) (void *handle, int width, int height); - void (*special) (void *handle, Telnet_Special code); - const struct telnet_special *(*get_specials) (void *handle); - int (*connected) (void *handle); - int (*exitcode) (void *handle); - /* If back->sendok() returns FALSE, data sent to it from the frontend - * may be lost. */ - int (*sendok) (void *handle); - int (*ldisc) (void *handle, int); - void (*provide_ldisc) (void *handle, void *ldisc); - void (*provide_logctx) (void *handle, void *logctx); - /* - * back->unthrottle() tells the back end that the front end - * buffer is clearing. - */ - void (*unthrottle) (void *handle, int); - int (*cfg_info) (void *handle); +struct Backend { + const BackendVtable *vt; +}; +struct BackendVtable { + const char *(*init) (Seat *seat, Backend **backend_out, + LogContext *logctx, Conf *conf, + const char *host, int port, + char **realhost, bool nodelay, bool keepalive); + + void (*free) (Backend *be); + /* Pass in a replacement configuration. */ + void (*reconfig) (Backend *be, Conf *conf); + /* send() returns the current amount of buffered data. */ + int (*send) (Backend *be, const char *buf, int len); + /* sendbuffer() does the same thing but without attempting a send */ + int (*sendbuffer) (Backend *be); + void (*size) (Backend *be, int width, int height); + void (*special) (Backend *be, SessionSpecialCode code, int arg); + const SessionSpecial *(*get_specials) (Backend *be); + bool (*connected) (Backend *be); + 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). */ + bool (*sendok) (Backend *be); + bool (*ldisc_option_state) (Backend *be, int); + void (*provide_ldisc) (Backend *be, Ldisc *ldisc); + /* Tells the back end that the front end buffer is clearing. */ + void (*unthrottle) (Backend *be, int bufsize); + int (*cfg_info) (Backend *be); + /* Only implemented in the SSH protocol: check whether a * connection-sharing upstream exists for a given configuration. */ - int (*test_for_upstream)(const char *host, int port, Conf *conf); + bool (*test_for_upstream)(const char *host, int port, Conf *conf); + const char *name; int protocol; int default_port; }; -extern Backend *backends[]; +#define backend_init(vt, seat, out, logctx, conf, host, port, rhost, nd, ka) \ + ((vt)->init(seat, out, logctx, conf, host, port, rhost, nd, ka)) +#define backend_free(be) ((be)->vt->free(be)) +#define backend_reconfig(be, conf) ((be)->vt->reconfig(be, conf)) +#define backend_send(be, buf, len) ((be)->vt->send(be, buf, len)) +#define backend_sendbuffer(be) ((be)->vt->sendbuffer(be)) +#define backend_size(be, w, h) ((be)->vt->size(be, w, h)) +#define backend_special(be, code, arg) ((be)->vt->special(be, code, arg)) +#define backend_get_specials(be) ((be)->vt->get_specials(be)) +#define backend_connected(be) ((be)->vt->connected(be)) +#define backend_exitcode(be) ((be)->vt->exitcode(be)) +#define backend_sendok(be) ((be)->vt->sendok(be)) +#define backend_ldisc_option_state(be, opt) \ + ((be)->vt->ldisc_option_state(be, opt)) +#define backend_provide_ldisc(be, ldisc) ((be)->vt->provide_ldisc(be, ldisc)) +#define backend_unthrottle(be, bufsize) ((be)->vt->unthrottle(be, bufsize)) +#define backend_cfg_info(be) ((be)->vt->cfg_info(be)) + +extern const struct BackendVtable *const backends[]; /* * Suggested default protocol provided by the backend link module. @@ -491,10 +567,6 @@ extern const char *const appname; * * FLAG_VERBOSE is set when the user requests verbose details. * - * FLAG_STDERR is set in command-line applications (which have a - * functioning stderr that it makes sense to write to) and not in - * GUI applications (which don't). - * * FLAG_INTERACTIVE is set when a full interactive shell session is * being run, _either_ because no remote command has been provided * _or_ because the application is GUI and can't run non- @@ -509,8 +581,7 @@ extern const char *const appname; * avoid collision. */ #define FLAG_VERBOSE 0x0001 -#define FLAG_STDERR 0x0002 -#define FLAG_INTERACTIVE 0x0004 +#define FLAG_INTERACTIVE 0x0002 GLOBAL int flags; /* @@ -522,16 +593,14 @@ GLOBAL int default_protocol; GLOBAL int default_port; /* - * This is set TRUE by cmdline.c iff a session is loaded with "-load". + * This is set true by cmdline.c iff a session is loaded with "-load". */ -GLOBAL int loaded_session; +GLOBAL bool loaded_session; /* * This is set to the name of the loaded session. */ GLOBAL char *cmdline_session_name; -struct RSAKey; /* be a little careful of scope */ - /* * Mechanism for getting text strings such as usernames and passwords * from the front-end. @@ -550,7 +619,7 @@ struct RSAKey; /* be a little careful of scope */ */ typedef struct { char *prompt; - int echo; + bool echo; /* * 'result' must be a dynamically allocated array of exactly * 'resultsize' chars. The code for actually reading input may @@ -573,106 +642,509 @@ typedef struct { * information (so the caller should ensure that the supplied text is * sufficient). */ - int to_server; + bool to_server; char *name; /* Short description, perhaps for dialog box title */ - int name_reqd; /* Display of `name' required or optional? */ + bool name_reqd; /* Display of `name' required or optional? */ char *instruction; /* Long description, maybe with embedded newlines */ - int instr_reqd; /* Display of `instruction' required or optional? */ + bool instr_reqd; /* Display of `instruction' required or optional? */ size_t n_prompts; /* May be zero (in which case display the foregoing, * if any, and return success) */ prompt_t **prompts; - void *frontend; void *data; /* slot for housekeeping data, managed by - * get_userpass_input(); initially NULL */ + * seat_get_userpass_input(); initially NULL */ } prompts_t; -prompts_t *new_prompts(void *frontend); -void add_prompt(prompts_t *p, char *promptstr, int echo); +prompts_t *new_prompts(); +void add_prompt(prompts_t *p, char *promptstr, bool echo); void prompt_set_result(prompt_t *pr, const char *newstr); void prompt_ensure_result_size(prompt_t *pr, int len); /* Burn the evidence. (Assumes _all_ strings want free()ing.) */ void free_prompts(prompts_t *p); /* - * Exports from the front end. + * Data type definitions for true-colour terminal display. + * 'optionalrgb' describes a single RGB colour, which overrides the + * other colour settings if 'enabled' is nonzero, and is ignored + * otherwise. 'truecolour' contains a pair of those for foreground and + * background. */ -void request_resize(void *frontend, int, int); -void do_text(Context, int, int, wchar_t *, int, unsigned long, int); -void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int); -int char_width(Context ctx, int uc); -#ifdef OPTIMISE_SCROLL -void do_scroll(Context, int, int, int); -#endif -void set_title(void *frontend, char *); -void set_icon(void *frontend, char *); -void set_sbar(void *frontend, int, int, int); -Context get_ctx(void *frontend); -void free_ctx(Context); -void palette_set(void *frontend, int, int, int, int); -void palette_reset(void *frontend); -void write_aclip(void *frontend, char *, int, int); -void write_clip(void *frontend, wchar_t *, int *, int, int); -void get_clip(void *frontend, wchar_t **, int *); -void optimised_move(void *frontend, int, int, int); -void set_raw_mouse_mode(void *frontend, int); -void connection_fatal(void *frontend, const char *, ...); -void nonfatal(const char *, ...); -void fatalbox(const char *, ...); -void modalfatalbox(const char *, ...); -#ifdef macintosh -#pragma noreturn(fatalbox) -#pragma noreturn(modalfatalbox) -#endif -void do_beep(void *frontend, int); -void begin_session(void *frontend); -void sys_cursor(void *frontend, int x, int y); -void request_paste(void *frontend); -void frontend_keypress(void *frontend); -void frontend_echoedit_update(void *frontend, int echo, int edit); -/* It's the backend's responsibility to invoke this at the start of a - * connection, if necessary; it can also invoke it later if the set of - * special commands changes. It does not need to invoke it at session - * shutdown. */ -void update_specials_menu(void *frontend); -int from_backend(void *frontend, int is_stderr, const char *data, int len); -int from_backend_untrusted(void *frontend, const char *data, int len); -/* Called when the back end wants to indicate that EOF has arrived on - * the server-to-client stream. Returns FALSE to indicate that we - * intend to keep the session open in the other direction, or TRUE to - * indicate that if they're closing so are we. */ -int from_backend_eof(void *frontend); -void notify_remote_exit(void *frontend); -/* Get a sensible value for a tty mode. NULL return = don't set. - * Otherwise, returned value should be freed by caller. */ -char *get_ttymode(void *frontend, const char *mode); +typedef struct optionalrgb { + bool enabled; + unsigned char r, g, b; +} optionalrgb; +extern const optionalrgb optionalrgb_none; +typedef struct truecolour { + optionalrgb fg, bg; +} truecolour; +#define optionalrgb_equal(r1,r2) ( \ + (r1).enabled==(r2).enabled && \ + (r1).r==(r2).r && (r1).g==(r2).g && (r1).b==(r2).b) +#define truecolour_equal(c1,c2) ( \ + optionalrgb_equal((c1).fg, (c2).fg) && \ + optionalrgb_equal((c1).bg, (c2).bg)) + /* - * >0 = `got all results, carry on' - * 0 = `user cancelled' (FIXME distinguish "give up entirely" and "next auth"?) - * <0 = `please call back later with more in/inlen' + * Enumeration of clipboards. We provide some standard ones cross- + * platform, and then permit each platform to extend this enumeration + * further by defining PLATFORM_CLIPBOARDS in its own header file. + * + * CLIP_NULL is a non-clipboard, writes to which are ignored and reads + * from which return no data. + * + * CLIP_LOCAL refers to a buffer within terminal.c, which + * unconditionally saves the last data selected in the terminal. In + * configurations where a system clipboard is not written + * automatically on selection but instead by an explicit UI action, + * this is where the code responding to that action can find the data + * to write to the clipboard in question. */ -int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen); -#define OPTIMISE_IS_SCROLL 1 - -void set_iconic(void *frontend, int iconic); -void move_window(void *frontend, int x, int y); -void set_zorder(void *frontend, int top); -void refresh_window(void *frontend); -void set_zoomed(void *frontend, int zoomed); -int is_iconic(void *frontend); -void get_window_pos(void *frontend, int *x, int *y); -void get_window_pixels(void *frontend, int *x, int *y); -char *get_window_title(void *frontend, int icon); -/* Hint from backend to frontend about time-consuming operations. - * Initial state is assumed to be BUSY_NOT. */ -enum { +#define CROSS_PLATFORM_CLIPBOARDS(X) \ + X(CLIP_NULL, "null clipboard") \ + X(CLIP_LOCAL, "last text selected in terminal") \ + /* end of list */ + +#define ALL_CLIPBOARDS(X) \ + CROSS_PLATFORM_CLIPBOARDS(X) \ + PLATFORM_CLIPBOARDS(X) \ + /* end of list */ + +#define CLIP_ID(id,name) id, +enum { ALL_CLIPBOARDS(CLIP_ID) N_CLIPBOARDS }; +#undef CLIP_ID + +/* Hint from backend to frontend about time-consuming operations, used + * by seat_set_busy_status. Initial state is assumed to be + * BUSY_NOT. */ +typedef enum BusyStatus { BUSY_NOT, /* Not busy, all user interaction OK */ - BUSY_WAITING, /* Waiting for something; local event loops still running - so some local interaction (e.g. menus) OK, but network - stuff is suspended */ - BUSY_CPU /* Locally busy (e.g. crypto); user interaction suspended */ + BUSY_WAITING, /* Waiting for something; local event loops still + running so some local interaction (e.g. menus) + OK, but network stuff is suspended */ + BUSY_CPU /* Locally busy (e.g. crypto); user interaction + * suspended */ +} BusyStatus; + +/* + * Data type 'Seat', which is an API intended to contain essentially + * everything that a back end might need to talk to its client for: + * session output, password prompts, SSH warnings about host keys and + * weak cryptography, notifications of events like the remote process + * exiting or the GUI specials menu needing an update. + */ +struct Seat { + const struct SeatVtable *vt; }; -void set_busy_status(void *frontend, int status); -int frontend_is_utf8(void *frontend); +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. + * + * The return value is the current size of the output backlog. + */ + int (*output)(Seat *seat, bool is_stderr, const void *data, int len); + + /* + * Called when the back end wants to indicate that EOF has arrived + * on the server-to-client stream. Returns false to indicate that + * we intend to keep the session open in the other direction, or + * true to indicate that if they're closing so are we. + */ + bool (*eof)(Seat *seat); + + /* + * 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). + * + * 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 + * 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 + * 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, bufchain *input); + + /* + * Notify the seat that the process running at the other end of + * the connection has finished. + */ + void (*notify_remote_exit)(Seat *seat); + + /* + * Notify the seat that the connection has suffered a fatal error. + */ + void (*connection_fatal)(Seat *seat, const char *message); + + /* + * Notify the seat that the list of special commands available + * from backend_get_specials() has changed, so that it might want + * to call that function to repopulate its menu. + * + * Seats are not expected to call backend_get_specials() + * proactively; they may start by assuming that the backend + * provides no special commands at all, so if the backend does + * provide any, then it should use this notification at startup + * time. Of course it can also invoke it later if the set of + * special commands changes. + * + * It does not need to invoke it at session shutdown. + */ + void (*update_specials_menu)(Seat *seat); + /* + * Get the seat's preferred value for an SSH terminal mode + * setting. Returning NULL indicates no preference (i.e. the SSH + * connection will not attempt to set the mode at all). + * + * The returned value is dynamically allocated, and the caller + * should free it. + */ + char *(*get_ttymode)(Seat *seat, const char *mode); + + /* + * Tell the seat whether the backend is currently doing anything + * CPU-intensive (typically a cryptographic key exchange). See + * BusyStatus enumeration above. + */ + void (*set_busy_status)(Seat *seat, BusyStatus status); + + /* + * 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. + * + * Return values: + * + * - +1 means `key was OK' (either already known or the user just + * approved it) `so continue with the connection' + * + * - 0 means `key was not OK, abandon the connection' + * + * - -1 means `I've initiated enquiries, please wait to be called + * back via the provided function with a result that's either 0 + * or +1'. + */ + int (*verify_ssh_host_key)( + Seat *seat, const char *host, int port, + const char *keytype, char *keystr, char *key_fingerprint, + 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. + */ + int (*confirm_weak_crypto_primitive)( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); + + /* + * Variant form of confirm_weak_crypto_primitive, which prints a + * slightly different message but otherwise has the same + * semantics. + * + * This form is used in the case where we're using a host key + * below the warning threshold because that's the best one we have + * cached, but at least one host key algorithm *above* the + * threshold is available that we don't have cached. 'betteralgs' + * lists the better algorithm(s). + */ + int (*confirm_weak_cached_hostkey)( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); + + /* + * Indicates whether the seat is expecting to interact with the + * user in the UTF-8 character set. (Affects e.g. visual erase + * handling in local line editing.) + */ + bool (*is_utf8)(Seat *seat); + + /* + * Notify the seat that the back end, and/or the ldisc between + * them, have changed their idea of whether they currently want + * local echo and/or local line editing enabled. + */ + void (*echoedit_update)(Seat *seat, bool echoing, bool editing); + + /* + * Return the local X display string relevant to a seat, or NULL + * if there isn't one or if the concept is meaningless. + */ + const char *(*get_x_display)(Seat *seat); + + /* + * Return the X11 id of the X terminal window relevant to a seat, + * by returning true and filling in the output pointer. Return + * false if there isn't one or if the concept is meaningless. + */ + bool (*get_windowid)(Seat *seat, long *id_out); + + /* + * Return the size of the terminal window in pixels. If the + * concept is meaningless or the information is unavailable, + * return false; otherwise fill in the output pointers and return + * true. + */ + bool (*get_window_pixel_size)(Seat *seat, int *width, int *height); +}; + +#define seat_output(seat, is_stderr, data, len) \ + ((seat)->vt->output(seat, is_stderr, data, len)) +#define seat_eof(seat) \ + ((seat)->vt->eof(seat)) +#define seat_get_userpass_input(seat, p, input) \ + ((seat)->vt->get_userpass_input(seat, p, input)) +#define seat_notify_remote_exit(seat) \ + ((seat)->vt->notify_remote_exit(seat)) +#define seat_update_specials_menu(seat) \ + ((seat)->vt->update_specials_menu(seat)) +#define seat_get_ttymode(seat, mode) \ + ((seat)->vt->get_ttymode(seat, mode)) +#define seat_set_busy_status(seat, status) \ + ((seat)->vt->set_busy_status(seat, status)) +#define seat_verify_ssh_host_key(seat, h, p, typ, str, fp, cb, ctx) \ + ((seat)->vt->verify_ssh_host_key(seat, h, p, typ, str, fp, cb, ctx)) +#define seat_confirm_weak_crypto_primitive(seat, typ, alg, cb, ctx) \ + ((seat)->vt->confirm_weak_crypto_primitive(seat, typ, alg, cb, ctx)) +#define seat_confirm_weak_cached_hostkey(seat, alg, better, cb, ctx) \ + ((seat)->vt->confirm_weak_cached_hostkey(seat, alg, better, cb, ctx)) +#define seat_is_utf8(seat) \ + ((seat)->vt->is_utf8(seat)) +#define seat_echoedit_update(seat, echoing, editing) \ + ((seat)->vt->echoedit_update(seat, echoing, editing)) +#define seat_get_x_display(seat) \ + ((seat)->vt->get_x_display(seat)) +#define seat_get_windowid(seat, out) \ + ((seat)->vt->get_windowid(seat, out)) +#define seat_get_window_pixel_size(seat, width, height) \ + ((seat)->vt->get_window_pixel_size(seat, width, height)) + +/* 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. */ +void seat_connection_fatal(Seat *seat, const char *fmt, ...); + +/* Handy aliases for seat_output which set is_stderr to a fixed value. */ +#define seat_stdout(seat, data, len) \ + seat_output(seat, false, data, len) +#define seat_stderr(seat, data, len) \ + seat_output(seat, true, data, len) + +/* + * Stub methods for seat implementations that want to use the obvious + * null handling for a given method. + * + * These are generally obvious, except for is_utf8, where you might + * plausibly want to return either fixed answer 'no' or 'yes'. + */ +int nullseat_output(Seat *seat, bool is_stderr, const void *data, int len); +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_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( + Seat *seat, const char *host, int port, + const char *keytype, char *keystr, char *key_fingerprint, + void (*callback)(void *ctx, int result), void *ctx); +int 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( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int 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); +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); + +/* + * Seat functions provided by the platform's console-application + * support module (wincons.c, uxcons.c). + */ + +void console_connection_fatal(Seat *seat, const char *message); +int console_verify_ssh_host_key( + Seat *seat, const char *host, int port, + const char *keytype, char *keystr, char *key_fingerprint, + void (*callback)(void *ctx, int result), void *ctx); +int 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( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); + +/* + * Other centralised seat functions. + */ +int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); + +/* + * Data type 'TermWin', which is a vtable encapsulating all the + * functionality that Terminal expects from its containing terminal + * window. + */ +struct TermWin { + const struct TermWinVtable *vt; +}; +struct TermWinVtable { + /* + * All functions listed here between setup_draw_ctx and + * free_draw_ctx expect to be _called_ between them too, so that + * the TermWin has a drawing context currently available. + * + * (Yes, even char_width, because e.g. the Windows implementation + * of TermWin handles it by loading the currently configured font + * into the HDC and doing a GDI query.) + */ + bool (*setup_draw_ctx)(TermWin *); + /* Draw text in the window, during a painting operation */ + void (*draw_text)(TermWin *, int x, int y, wchar_t *text, int len, + unsigned long attrs, int line_attrs, truecolour tc); + /* Draw the visible cursor. Expects you to have called do_text + * first (because it might just draw an underline over a character + * presumed to exist already), but also expects you to pass in all + * the details of the character under the cursor (because it might + * redraw it in different colours). */ + void (*draw_cursor)(TermWin *, int x, int y, wchar_t *text, int len, + unsigned long attrs, int line_attrs, truecolour tc); + int (*char_width)(TermWin *, int uc); + void (*free_draw_ctx)(TermWin *); + + void (*set_cursor_pos)(TermWin *, int x, int y); + + void (*set_raw_mouse_mode)(TermWin *, bool enable); + + void (*set_scrollbar)(TermWin *, int total, int start, int page); + + void (*bell)(TermWin *, int mode); + + void (*clip_write)(TermWin *, int clipboard, wchar_t *text, int *attrs, + truecolour *colours, int len, bool must_deselect); + void (*clip_request_paste)(TermWin *, int clipboard); + + void (*refresh)(TermWin *); + + void (*request_resize)(TermWin *, int w, int h); + + void (*set_title)(TermWin *, const char *title); + void (*set_icon_title)(TermWin *, const char *icontitle); + /* 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 + * the window it remembers whether to go back to normal or + * maximised. */ + void (*set_minimised)(TermWin *, bool minimised); + bool (*is_minimised)(TermWin *); + void (*set_maximised)(TermWin *, bool maximised); + void (*move)(TermWin *, int x, int y); + void (*set_zorder)(TermWin *, bool top); + + bool (*palette_get)(TermWin *, int n, int *r, int *g, int *b); + void (*palette_set)(TermWin *, int n, int r, int g, int b); + void (*palette_reset)(TermWin *); + + void (*get_pos)(TermWin *, int *x, int *y); + void (*get_pixels)(TermWin *, int *x, int *y); + const char *(*get_title)(TermWin *, bool icon); + bool (*is_utf8)(TermWin *); +}; + +#define win_setup_draw_ctx(win) \ + ((win)->vt->setup_draw_ctx(win)) +#define win_draw_text(win, x, y, text, len, attrs, lattrs, tc) \ + ((win)->vt->draw_text(win, x, y, text, len, attrs, lattrs, tc)) +#define win_draw_cursor(win, x, y, text, len, attrs, lattrs, tc) \ + ((win)->vt->draw_cursor(win, x, y, text, len, attrs, lattrs, tc)) +#define win_char_width(win, uc) \ + ((win)->vt->char_width(win, uc)) +#define win_free_draw_ctx(win) \ + ((win)->vt->free_draw_ctx(win)) +#define win_set_cursor_pos(win, x, y) \ + ((win)->vt->set_cursor_pos(win, x, y)) +#define win_set_raw_mouse_mode(win, enable) \ + ((win)->vt->set_raw_mouse_mode(win, enable)) +#define win_set_scrollbar(win, total, start, page) \ + ((win)->vt->set_scrollbar(win, total, start, page)) +#define win_bell(win, mode) \ + ((win)->vt->bell(win, mode)) +#define win_clip_write(win, clipboard, text, attrs, colours, len, desel) \ + ((win)->vt->clip_write(win, clipboard, text, attrs, colours, len, desel)) +#define win_clip_request_paste(win, clipboard) \ + ((win)->vt->clip_request_paste(win, clipboard)) +#define win_refresh(win) \ + ((win)->vt->refresh(win)) +#define win_request_resize(win, w, h) \ + ((win)->vt->request_resize(win, w, h)) +#define win_set_title(win, title) \ + ((win)->vt->set_title(win, title)) +#define win_set_icon_title(win, ititle) \ + ((win)->vt->set_icon_title(win, ititle)) +#define win_set_minimised(win, minimised) \ + ((win)->vt->set_minimised(win, minimised)) +#define win_is_minimised(win) \ + ((win)->vt->is_minimised(win)) +#define win_set_maximised(win, maximised) \ + ((win)->vt->set_maximised(win, maximised)) +#define win_move(win, x, y) \ + ((win)->vt->move(win, x, y)) +#define win_set_zorder(win, top) \ + ((win)->vt->set_zorder(win, top)) +#define win_palette_get(win, n, r, g, b) \ + ((win)->vt->palette_get(win, n, r, g, b)) +#define win_palette_set(win, n, r, g, b) \ + ((win)->vt->palette_set(win, n, r, g, b)) +#define win_palette_reset(win) \ + ((win)->vt->palette_reset(win)) +#define win_get_pos(win, x, y) \ + ((win)->vt->get_pos(win, x, y)) +#define win_get_pixels(win, x, y) \ + ((win)->vt->get_pixels(win, x, y)) +#define win_get_title(win, icon) \ + ((win)->vt->get_title(win, icon)) +#define win_is_utf8(win) \ + ((win)->vt->is_utf8(win)) + +/* + * Global functions not specific to a connection instance. + */ +void nonfatal(const char *, ...); +void modalfatalbox(const char *, ...); +#ifdef macintosh +#pragma noreturn(modalfatalbox) +#endif void cleanup_exit(int); /* @@ -683,37 +1155,37 @@ void cleanup_exit(int); /* X(value-type, subkey-type, keyword) */ \ X(STR, NONE, host) \ X(INT, NONE, port) \ - X(INT, NONE, protocol) \ - X(INT, NONE, addressfamily) \ - X(INT, NONE, close_on_exit) \ - X(INT, NONE, warn_on_close) \ + X(INT, NONE, protocol) /* PROT_SSH, PROT_TELNET etc */ \ + X(INT, NONE, addressfamily) /* ADDRTYPE_IPV[46] or ADDRTYPE_UNSPEC */ \ + X(INT, NONE, close_on_exit) /* FORCE_ON, FORCE_OFF, AUTO */ \ + X(BOOL, NONE, warn_on_close) \ X(INT, NONE, ping_interval) /* in seconds */ \ - X(INT, NONE, tcp_nodelay) \ - X(INT, NONE, tcp_keepalives) \ + X(BOOL, NONE, tcp_nodelay) \ + X(BOOL, NONE, tcp_keepalives) \ X(STR, NONE, loghost) /* logical host being contacted, for host key check */ \ /* Proxy options */ \ X(STR, NONE, proxy_exclude_list) \ - X(INT, NONE, proxy_dns) \ - X(INT, NONE, even_proxy_localhost) \ - X(INT, NONE, proxy_type) \ + X(INT, NONE, proxy_dns) /* FORCE_ON, FORCE_OFF, AUTO */ \ + X(BOOL, NONE, even_proxy_localhost) \ + X(INT, NONE, proxy_type) /* PROXY_NONE, PROXY_SOCKS4, ... */ \ X(STR, NONE, proxy_host) \ X(INT, NONE, proxy_port) \ X(STR, NONE, proxy_username) \ X(STR, NONE, proxy_password) \ X(STR, NONE, proxy_telnet_command) \ - X(INT, NONE, proxy_log_to_term) \ + X(INT, NONE, proxy_log_to_term) /* FORCE_ON, FORCE_OFF, AUTO */ \ /* SSH options */ \ X(STR, NONE, remote_cmd) \ X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \ - X(INT, NONE, nopty) \ - X(INT, NONE, compression) \ + X(BOOL, NONE, nopty) \ + X(BOOL, NONE, compression) \ X(INT, INT, ssh_kexlist) \ X(INT, INT, ssh_hklist) \ X(INT, NONE, ssh_rekey_time) /* in minutes */ \ X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ - X(INT, NONE, tryagent) \ - X(INT, NONE, agentfwd) \ - X(INT, NONE, change_username) /* allow username switching in SSH-2 */ \ + X(BOOL, NONE, tryagent) \ + X(BOOL, NONE, agentfwd) \ + X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \ X(INT, INT, ssh_cipherlist) \ X(FILENAME, NONE, keyfile) \ /* \ @@ -730,18 +1202,20 @@ void cleanup_exit(int); * downgrades PuTTY. So it's easier to use these numbers internally too. \ */ \ X(INT, NONE, sshprot) \ - X(INT, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ - X(INT, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ - X(INT, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ - X(INT, NONE, try_tis_auth) \ - X(INT, NONE, try_ki_auth) \ - X(INT, NONE, try_gssapi_auth) /* attempt gssapi auth */ \ - X(INT, NONE, gssapifwd) /* forward tgt via gss */ \ + X(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_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ + X(BOOL, NONE, try_tis_auth) \ + X(BOOL, NONE, try_ki_auth) \ + X(BOOL, NONE, try_gssapi_auth) /* attempt gssapi auth via ssh userauth */ \ + X(BOOL, NONE, try_gssapi_kex) /* attempt gssapi auth via ssh kex */ \ + X(BOOL, NONE, gssapifwd) /* forward tgt via gss */ \ + X(INT, NONE, gssapirekey) /* KEXGSS refresh interval (mins) */ \ X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ X(FILENAME, NONE, ssh_gss_custom) \ - X(INT, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ - X(INT, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ - X(INT, NONE, ssh_no_shell) /* avoid running a shell */ \ + X(BOOL, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ + X(BOOL, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ + X(BOOL, NONE, ssh_no_shell) /* avoid running a shell */ \ X(STR, NONE, ssh_nc_host) /* host to connect to in `nc' mode */ \ X(INT, NONE, ssh_nc_port) /* port to connect to in `nc' mode */ \ /* Telnet options */ \ @@ -750,116 +1224,128 @@ void cleanup_exit(int); X(STR, STR, ttymodes) /* values are "Vvalue" or "A" */ \ X(STR, STR, environmt) \ X(STR, NONE, username) \ - X(INT, NONE, username_from_env) \ + X(BOOL, NONE, username_from_env) \ X(STR, NONE, localusername) \ - X(INT, NONE, rfc_environ) \ - X(INT, NONE, passive_telnet) \ + X(BOOL, NONE, rfc_environ) \ + X(BOOL, NONE, passive_telnet) \ /* Serial port options */ \ X(STR, NONE, serline) \ X(INT, NONE, serspeed) \ X(INT, NONE, serdatabits) \ X(INT, NONE, serstopbits) \ - X(INT, NONE, serparity) \ - X(INT, NONE, serflow) \ + X(INT, NONE, serparity) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \ + X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \ /* Keyboard options */ \ - X(INT, NONE, bksp_is_delete) \ - X(INT, NONE, rxvt_homeend) \ - X(INT, NONE, funky_type) \ - X(INT, NONE, no_applic_c) /* totally disable app cursor keys */ \ - X(INT, NONE, no_applic_k) /* totally disable app keypad */ \ - X(INT, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ - X(INT, NONE, no_remote_resize) /* disable remote resizing */ \ - X(INT, NONE, no_alt_screen) /* disable alternate screen */ \ - X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \ - X(INT, NONE, no_remote_clearscroll) /* disable ESC[3J */ \ - X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \ - X(INT, NONE, no_remote_charset) /* disable remote charset config */ \ - X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \ - X(INT, NONE, app_cursor) \ - X(INT, NONE, app_keypad) \ - X(INT, NONE, nethack_keypad) \ - X(INT, NONE, telnet_keyboard) \ - X(INT, NONE, telnet_newline) \ - X(INT, NONE, alt_f4) /* is it special? */ \ - X(INT, NONE, alt_space) /* is it special? */ \ - X(INT, NONE, alt_only) /* is it special? */ \ - X(INT, NONE, localecho) \ - X(INT, NONE, localedit) \ - X(INT, NONE, alwaysontop) \ - X(INT, NONE, fullscreenonaltenter) \ - X(INT, NONE, scroll_on_key) \ - X(INT, NONE, scroll_on_disp) \ - X(INT, NONE, erase_to_scrollback) \ - X(INT, NONE, compose_key) \ - X(INT, NONE, ctrlaltkeys) \ - X(INT, NONE, osx_option_meta) \ - X(INT, NONE, osx_command_meta) \ + X(BOOL, NONE, bksp_is_delete) \ + X(BOOL, NONE, rxvt_homeend) \ + X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \ + 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 */ \ + X(BOOL, NONE, no_remote_resize) /* disable remote resizing */ \ + X(BOOL, NONE, no_alt_screen) /* disable alternate screen */ \ + X(BOOL, NONE, no_remote_wintitle) /* disable remote retitling */ \ + X(BOOL, NONE, no_remote_clearscroll) /* disable ESC[3J */ \ + X(BOOL, NONE, no_dbackspace) /* disable destructive backspace */ \ + X(BOOL, NONE, no_remote_charset) /* disable remote charset config */ \ + X(INT, NONE, remote_qtitle_action) /* remote win title query action + * (TITLE_NONE, TITLE_EMPTY, ...) */ \ + X(BOOL, NONE, app_cursor) \ + X(BOOL, NONE, app_keypad) \ + X(BOOL, NONE, nethack_keypad) \ + X(BOOL, NONE, telnet_keyboard) \ + X(BOOL, NONE, telnet_newline) \ + X(BOOL, NONE, alt_f4) /* is it special? */ \ + X(BOOL, NONE, alt_space) /* is it special? */ \ + X(BOOL, NONE, alt_only) /* is it special? */ \ + X(INT, NONE, localecho) /* FORCE_ON, FORCE_OFF, AUTO */ \ + X(INT, NONE, localedit) /* FORCE_ON, FORCE_OFF, AUTO */ \ + X(BOOL, NONE, alwaysontop) \ + X(BOOL, NONE, fullscreenonaltenter) \ + X(BOOL, NONE, scroll_on_key) \ + X(BOOL, NONE, scroll_on_disp) \ + X(BOOL, NONE, erase_to_scrollback) \ + X(BOOL, NONE, compose_key) \ + X(BOOL, NONE, ctrlaltkeys) \ + X(BOOL, NONE, osx_option_meta) \ + X(BOOL, NONE, osx_command_meta) \ X(STR, NONE, wintitle) /* initial window title */ \ /* Terminal options */ \ X(INT, NONE, savelines) \ - X(INT, NONE, dec_om) \ - X(INT, NONE, wrap_mode) \ - X(INT, NONE, lfhascr) \ + X(BOOL, NONE, dec_om) \ + X(BOOL, NONE, wrap_mode) \ + X(BOOL, NONE, lfhascr) \ X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \ - X(INT, NONE, blink_cur) \ - X(INT, NONE, beep) \ - X(INT, NONE, beep_ind) \ - X(INT, NONE, bellovl) /* bell overload protection active? */ \ + X(BOOL, NONE, blink_cur) \ + X(INT, NONE, beep) /* BELL_DISABLED, BELL_DEFAULT, ... */ \ + X(INT, NONE, beep_ind) /* B_IND_DISABLED, B_IND_FLASH, ... */ \ + X(BOOL, NONE, bellovl) /* bell overload protection active? */ \ X(INT, NONE, bellovl_n) /* number of bells to cause overload */ \ X(INT, NONE, bellovl_t) /* time interval for overload (seconds) */ \ X(INT, NONE, bellovl_s) /* period of silence to re-enable bell (s) */ \ X(FILENAME, NONE, bell_wavefile) \ - X(INT, NONE, scrollbar) \ - X(INT, NONE, scrollbar_in_fullscreen) \ - X(INT, NONE, resize_action) \ - X(INT, NONE, bce) \ - X(INT, NONE, blinktext) \ - X(INT, NONE, win_name_always) \ + X(BOOL, NONE, scrollbar) \ + X(BOOL, NONE, scrollbar_in_fullscreen) \ + X(INT, NONE, resize_action) /* RESIZE_TERM, RESIZE_DISABLED, ... */ \ + X(BOOL, NONE, bce) \ + X(BOOL, NONE, blinktext) \ + X(BOOL, NONE, win_name_always) \ X(INT, NONE, width) \ X(INT, NONE, height) \ X(FONT, NONE, font) \ - X(INT, NONE, font_quality) \ + X(INT, NONE, font_quality) /* FQ_DEFAULT, FQ_ANTIALIASED, ... */ \ X(FILENAME, NONE, logfilename) \ - X(INT, NONE, logtype) \ - X(INT, NONE, logxfovr) \ - X(INT, NONE, logflush) \ - X(INT, NONE, logomitpass) \ - X(INT, NONE, logomitdata) \ - X(INT, NONE, hide_mouseptr) \ - X(INT, NONE, sunken_edge) \ - X(INT, NONE, window_border) \ + X(INT, NONE, logtype) /* LGTYP_NONE, LGTYPE_ASCII, ... */ \ + X(INT, NONE, logxfovr) /* LGXF_OVR, LGXF_APN, LGXF_ASK */ \ + X(BOOL, NONE, logflush) \ + X(BOOL, NONE, logheader) \ + X(BOOL, NONE, logomitpass) \ + X(BOOL, NONE, logomitdata) \ + X(BOOL, NONE, hide_mouseptr) \ + X(BOOL, NONE, sunken_edge) \ + X(INT, NONE, window_border) /* in pixels */ \ X(STR, NONE, answerback) \ X(STR, NONE, printer) \ - X(INT, NONE, arabicshaping) \ - X(INT, NONE, bidi) \ + X(BOOL, NONE, arabicshaping) \ + X(BOOL, NONE, bidi) \ /* Colour options */ \ - X(INT, NONE, ansi_colour) \ - X(INT, NONE, xterm_256_colour) \ - X(INT, NONE, system_colour) \ - X(INT, NONE, try_palette) \ - X(INT, NONE, bold_style) \ + X(BOOL, NONE, ansi_colour) \ + X(BOOL, NONE, xterm_256_colour) \ + X(BOOL, NONE, true_colour) \ + X(BOOL, NONE, system_colour) \ + X(BOOL, NONE, try_palette) \ + X(INT, NONE, bold_style) /* 1=font 2=colour (3=both) */ \ X(INT, INT, colours) \ /* Selection options */ \ - X(INT, NONE, mouse_is_xterm) \ - X(INT, NONE, rect_select) \ - X(INT, NONE, rawcnp) \ - X(INT, NONE, rtf_paste) \ - X(INT, NONE, mouse_override) \ + X(INT, NONE, mouse_is_xterm) /* 0=compromise 1=xterm 2=Windows */ \ + X(BOOL, NONE, rect_select) \ + X(BOOL, NONE, paste_controls) \ + X(BOOL, NONE, rawcnp) \ + X(BOOL, NONE, utf8linedraw) \ + X(BOOL, NONE, rtf_paste) \ + X(BOOL, NONE, mouse_override) \ X(INT, INT, wordness) \ + X(BOOL, NONE, mouseautocopy) \ + X(INT, NONE, mousepaste) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ + X(INT, NONE, ctrlshiftins) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ + X(INT, NONE, ctrlshiftcv) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ + X(STR, NONE, mousepaste_custom) \ + X(STR, NONE, ctrlshiftins_custom) \ + X(STR, NONE, ctrlshiftcv_custom) \ /* translations */ \ - X(INT, NONE, vtmode) \ + X(INT, NONE, vtmode) /* VT_XWINDOWS, VT_OEMANSI, ... */ \ X(STR, NONE, line_codepage) \ - X(INT, NONE, cjk_ambig_wide) \ - X(INT, NONE, utf8_override) \ - X(INT, NONE, xlat_capslockcyr) \ + X(BOOL, NONE, cjk_ambig_wide) \ + X(BOOL, NONE, utf8_override) \ + X(BOOL, NONE, xlat_capslockcyr) \ /* X11 forwarding */ \ - X(INT, NONE, x11_forward) \ + X(BOOL, NONE, x11_forward) \ X(STR, NONE, x11_display) \ - X(INT, NONE, x11_auth) \ + X(INT, NONE, x11_auth) /* X11_NO_AUTH, X11_MIT, X11_XDM */ \ X(FILENAME, NONE, xauthfile) \ /* port forwarding */ \ - X(INT, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ - X(INT, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \ + X(BOOL, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ + X(BOOL, NONE, rport_acceptall) /* same for remote forwarded ports (SSH-2 only) */ \ /* \ * Subkeys for 'portfwd' can have the following forms: \ * \ @@ -871,7 +1357,7 @@ void cleanup_exit(int); * should be of the form 'host:port'. \ */ \ X(STR, STR, portfwd) \ - /* SSH bug compatibility modes */ \ + /* SSH bug compatibility modes. All FORCE_ON/FORCE_OFF/AUTO */ \ X(INT, NONE, sshbug_ignore1) \ X(INT, NONE, sshbug_plainpw1) \ X(INT, NONE, sshbug_rsa1) \ @@ -890,10 +1376,10 @@ void cleanup_exit(int); * other than the main one, which means it can safely use a very \ * large window in SSH-2. \ */ \ - X(INT, NONE, ssh_simple) \ - X(INT, NONE, ssh_connection_sharing) \ - X(INT, NONE, ssh_connection_sharing_upstream) \ - X(INT, NONE, ssh_connection_sharing_downstream) \ + X(BOOL, NONE, ssh_simple) \ + X(BOOL, NONE, ssh_connection_sharing) \ + X(BOOL, NONE, ssh_connection_sharing_upstream) \ + X(BOOL, NONE, ssh_connection_sharing_downstream) \ /* * ssh_manual_hostkeys is conceptually a set rather than a * dictionary: the string subkeys are the important thing, and the @@ -901,16 +1387,17 @@ void cleanup_exit(int); */ \ X(STR, STR, ssh_manual_hostkeys) \ /* Options for pterm. Should split out into platform-dependent part. */ \ - X(INT, NONE, stamp_utmp) \ - X(INT, NONE, login_shell) \ - X(INT, NONE, scrollbar_on_left) \ - X(INT, NONE, shadowbold) \ + X(BOOL, NONE, stamp_utmp) \ + X(BOOL, NONE, login_shell) \ + X(BOOL, NONE, scrollbar_on_left) \ + X(BOOL, NONE, shadowbold) \ X(FONT, NONE, boldfont) \ X(FONT, NONE, widefont) \ X(FONT, NONE, wideboldfont) \ - X(INT, NONE, shadowboldoffset) \ - X(INT, NONE, crhaslf) \ + X(INT, NONE, shadowboldoffset) /* in pixels */ \ + X(BOOL, NONE, crhaslf) \ X(STR, NONE, winclass) \ + /* end of list */ /* Now define the actual enum of option keywords using that macro. */ #define CONF_ENUM_DEF(valtype, keytype, keyword) CONF_ ## keyword, @@ -925,6 +1412,7 @@ void conf_free(Conf *conf); Conf *conf_copy(Conf *oldconf); void conf_copy_into(Conf *dest, Conf *src); /* Mandatory accessor functions: enforce by assertion that keys exist. */ +bool conf_get_bool(Conf *conf, int key); int conf_get_int(Conf *conf, int key); int conf_get_int_int(Conf *conf, int key, int subkey); char *conf_get_str(Conf *conf, int key); /* result still owned by conf */ @@ -941,6 +1429,7 @@ char *conf_get_str_strs(Conf *conf, int key, char *subkeyin, char **subkeyout); /* Return the nth string subkey in a list. Owned by conf. NULL if beyond end */ char *conf_get_str_nthstrkey(Conf *conf, int key, int n); /* Functions to set entries in configuration. Always copy their inputs. */ +void conf_set_bool(Conf *conf, int key, bool value); void conf_set_int(Conf *conf, int key, int value); void conf_set_int_int(Conf *conf, int key, int subkey, int value); void conf_set_str(Conf *conf, int key, const char *value); @@ -950,9 +1439,8 @@ void conf_del_str_str(Conf *conf, int key, const char *subkey); void conf_set_filename(Conf *conf, int key, const Filename *val); void conf_set_fontspec(Conf *conf, int key, const FontSpec *val); /* Serialisation functions for Duplicate Session */ -int conf_serialised_size(Conf *conf); -void conf_serialise(Conf *conf, void *data); -int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/ +void conf_serialise(BinarySink *bs, Conf *conf); +bool conf_deserialise(Conf *conf, BinarySource *src);/*returns true on success*/ /* * Functions to copy, free, serialise and deserialise FontSpecs. @@ -965,8 +1453,8 @@ int conf_deserialise(Conf *conf, void *data, int maxsize);/*returns size used*/ */ FontSpec *fontspec_copy(const FontSpec *f); void fontspec_free(FontSpec *f); -int fontspec_serialise(FontSpec *f, void *data); -FontSpec *fontspec_deserialise(void *data, int maxsize, int *used); +void fontspec_serialise(BinarySink *bs, FontSpec *f); +FontSpec *fontspec_deserialise(BinarySource *src); /* * Exports from noise.c. @@ -981,14 +1469,14 @@ void random_destroy_seed(void); /* * Exports from settings.c. */ -Backend *backend_from_name(const char *name); -Backend *backend_from_proto(int proto); +const struct BackendVtable *backend_vt_from_name(const char *name); +const struct BackendVtable *backend_vt_from_proto(int proto); char *get_remote_username(Conf *conf); /* dynamically allocated */ char *save_settings(const char *section, Conf *conf); -void save_open_settings(void *sesskey, Conf *conf); +void save_open_settings(settings_w *sesskey, Conf *conf); void load_settings(const char *section, Conf *conf); -void load_open_settings(void *sesskey, Conf *conf); -void get_sesslist(struct sesslist *, int allocate); +void load_open_settings(settings_r *sesskey, Conf *conf); +void get_sesslist(struct sesslist *, bool allocate); void do_defaults(const char *, Conf *); void registry_cleanup(void); @@ -1008,6 +1496,7 @@ void registry_cleanup(void); * transferred to the caller, and must be freed. */ char *platform_default_s(const char *name); +bool platform_default_b(const char *name, bool def); int platform_default_i(const char *name, int def); Filename *platform_default_filename(const char *name); FontSpec *platform_default_fontspec(const char *name); @@ -1016,52 +1505,102 @@ FontSpec *platform_default_fontspec(const char *name); * Exports from terminal.c. */ -Terminal *term_init(Conf *, struct unicode_data *, void *); +Terminal *term_init(Conf *, struct unicode_data *, TermWin *); void term_free(Terminal *); void term_size(Terminal *, int, int, int); -void term_paint(Terminal *, Context, int, int, int, int, int); +void term_paint(Terminal *, int, int, int, int, bool); void term_scroll(Terminal *, int, int); void term_scroll_to_selection(Terminal *, int); -void term_pwron(Terminal *, int); +void term_pwron(Terminal *, bool); void term_clrsb(Terminal *); void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, - int,int,int,int,int); + int, int, bool, bool, bool); void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, unsigned int); -void term_deselect(Terminal *); +void term_lost_clipboard_ownership(Terminal *, int clipboard); void term_update(Terminal *); void term_invalidate(Terminal *); -void term_blink(Terminal *, int set_cursor); -void term_do_paste(Terminal *); +void term_blink(Terminal *, bool set_cursor); +void term_do_paste(Terminal *, const wchar_t *, int); void term_nopaste(Terminal *); -int term_ldisc(Terminal *, int option); -void term_copyall(Terminal *); +bool term_ldisc(Terminal *, int option); +void term_copyall(Terminal *, const int *, int); 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 *); -int term_data(Terminal *, int is_stderr, const char *data, int len); -int term_data_untrusted(Terminal *, const char *data, int len); -void term_provide_resize_fn(Terminal *term, - void (*resize_fn)(void *, int, int), - void *resize_ctx); -void term_provide_logctx(Terminal *term, void *logctx); -void term_set_focus(Terminal *term, int has_focus); +int term_data(Terminal *, bool is_stderr, const void *data, int 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); char *term_get_ttymode(Terminal *term, const char *mode); -int term_get_userpass_input(Terminal *term, prompts_t *p, - const unsigned char *in, int inlen); +int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input); -int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl); +int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl); /* * Exports from logging.c. */ -void *log_init(void *frontend, Conf *conf); -void log_free(void *logctx); -void log_reconfig(void *logctx, Conf *conf); -void logfopen(void *logctx); -void logfclose(void *logctx); -void logtraffic(void *logctx, unsigned char c, int logmode); -void logflush(void *logctx); -void log_eventlog(void *logctx, const char *string); +struct LogPolicyVtable { + /* + * Pass Event Log entries on from LogContext to the front end, + * which might write them to standard error or save them for a GUI + * list box or other things. + */ + void (*eventlog)(LogPolicy *lp, const char *event); + + /* + * Ask what to do about the specified output log file already + * existing. Can return four values: + * + * - 2 means overwrite the log file + * - 1 means append to the log file + * - 0 means cancel logging for this session + * - -1 means please wait, and callback() will be called with one + * of those options. + */ + int (*askappend)(LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx); + + /* + * Emergency logging when the log file itself can't be opened, + * which typically means we want to shout about it more loudly + * than a mere Event Log entry. + * + * One reasonable option is to send it to the same place that + * stderr output from the main session goes (so, either a console + * tool's actual stderr, or a terminal window). In many cases this + * is unlikely to cause this error message to turn up + * embarrassingly in a log file of real server output, because the + * whole point is that we haven't managed to open any such log + * file :-) + */ + void (*logging_error)(LogPolicy *lp, const char *event); +}; +struct LogPolicy { + const LogPolicyVtable *vt; +}; +#define lp_eventlog(lp, event) ((lp)->vt->eventlog(lp, event)) +#define lp_askappend(lp, fn, cb, ctx) ((lp)->vt->askappend(lp, fn, cb, ctx)) +#define lp_logging_error(lp, event) ((lp)->vt->logging_error(lp, event)) + +LogContext *log_init(LogPolicy *lp, Conf *conf); +void log_free(LogContext *logctx); +void log_reconfig(LogContext *logctx, Conf *conf); +void logfopen(LogContext *logctx); +void logfclose(LogContext *logctx); +void logtraffic(LogContext *logctx, unsigned char c, int logmode); +void logflush(LogContext *logctx); +void logevent(LogContext *logctx, const char *event); +void logeventf(LogContext *logctx, const char *fmt, ...); +void logeventvf(LogContext *logctx, const char *fmt, va_list ap); + +/* + * Pass a dynamically allocated string to logevent and immediately + * free it. Intended for use by wrapper macros which pass the return + * value of dupprintf straight to this. + */ +void logevent_and_free(LogContext *logctx, char *event); enum { PKT_INCOMING, PKT_OUTGOING }; enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT }; struct logblank_t { @@ -1069,57 +1608,61 @@ struct logblank_t { int len; int type; }; -void log_packet(void *logctx, int direction, int type, +void log_packet(LogContext *logctx, int direction, int type, const char *texttype, const void *data, int len, int n_blanks, const struct logblank_t *blanks, const unsigned long *sequence, unsigned downstream_id, const char *additional_log_text); +/* This is defined by applications that have an obvious logging + * destination like standard error or the GUI. */ +extern LogPolicy default_logpolicy[1]; + /* * Exports from testback.c */ -extern Backend null_backend; -extern Backend loop_backend; +extern const struct BackendVtable null_backend; +extern const struct BackendVtable loop_backend; /* * Exports from raw.c. */ -extern Backend raw_backend; +extern const struct BackendVtable raw_backend; /* * Exports from rlogin.c. */ -extern Backend rlogin_backend; +extern const struct BackendVtable rlogin_backend; /* * Exports from telnet.c. */ -extern Backend telnet_backend; +extern const struct BackendVtable telnet_backend; /* * Exports from ssh.c. */ -extern Backend ssh_backend; +extern const struct BackendVtable ssh_backend; /* * Exports from ldisc.c. */ -void *ldisc_create(Conf *, Terminal *, Backend *, void *, void *); -void ldisc_configure(void *, Conf *); -void ldisc_free(void *); -void ldisc_send(void *handle, const char *buf, int len, int interactive); -void ldisc_echoedit_update(void *handle); +Ldisc *ldisc_create(Conf *, Terminal *, Backend *, Seat *); +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 *); /* * Exports from ldiscucs.c. */ -void lpage_send(void *, int codepage, const char *buf, int len, - int interactive); -void luni_send(void *, const wchar_t * widebuf, int len, int interactive); +void lpage_send(Ldisc *, int codepage, const char *buf, int len, + bool interactive); +void luni_send(Ldisc *, const wchar_t * widebuf, int len, bool interactive); /* * Exports from sshrand.c. @@ -1138,29 +1681,35 @@ void random_unref(void); /* * Exports from pinger.c. */ -typedef struct pinger_tag *Pinger; -Pinger pinger_new(Conf *conf, Backend *back, void *backhandle); -void pinger_reconfig(Pinger, Conf *oldconf, Conf *newconf); -void pinger_free(Pinger); +typedef struct Pinger Pinger; +Pinger *pinger_new(Conf *conf, Backend *backend); +void pinger_reconfig(Pinger *, Conf *oldconf, Conf *newconf); +void pinger_free(Pinger *); /* * Exports from misc.c. */ #include "misc.h" -int conf_launchable(Conf *conf); +bool conf_launchable(Conf *conf); char const *conf_dest(Conf *conf); +/* + * Exports from sessprep.c. + */ +void prepare_session(Conf *conf); + /* * Exports from sercfg.c. */ -void ser_setup_config_box(struct controlbox *b, int midsession, +void ser_setup_config_box(struct controlbox *b, bool midsession, int parity_mask, int flow_mask); /* * Exports from version.c. */ extern const char ver[]; +extern const char commitid[]; /* * Exports from unicode.c. @@ -1169,11 +1718,11 @@ extern const char ver[]; #define CP_UTF8 65001 #endif /* void init_ucs(void); -- this is now in platform-specific headers */ -int is_dbcs_leadbyte(int codepage, char byte); +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, int *defused, + char *mbstr, int mblen, const char *defchr, struct unicode_data *ucsdata); wchar_t xlat_uskbd2cyrllic(int ch); int check_compose(int first, int second); @@ -1190,14 +1739,6 @@ int mk_wcswidth(const unsigned int *pwcs, size_t n); int mk_wcwidth_cjk(unsigned int ucs); int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); -/* - * Exports from mscrypto.c - */ -#ifdef MSCRYPTOAPI -int crypto_startup(); -void crypto_wrapup(); -#endif - /* * Exports from pageantc.c. * @@ -1223,76 +1764,41 @@ void crypto_wrapup(); */ typedef struct agent_pending_query agent_pending_query; agent_pending_query *agent_query( - void *in, int inlen, void **out, int *outlen, + strbuf *in, void **out, int *outlen, void (*callback)(void *, void *, int), void *callback_ctx); void agent_cancel_query(agent_pending_query *); -void agent_query_synchronous(void *in, int inlen, void **out, int *outlen); -int agent_exists(void); +void agent_query_synchronous(strbuf *in, void **out, int *outlen); +bool agent_exists(void); /* * Exports from wildcard.c */ const char *wc_error(int value); +int wc_match_pl(const char *wildcard, ptrlen target); int wc_match(const char *wildcard, const char *target); -int wc_unescape(char *output, const char *wildcard); +bool wc_unescape(char *output, const char *wildcard); /* * Exports from frontend (windlg.c etc) */ -void logevent(void *frontend, const char *); void pgp_fingerprints(void); -/* - * verify_ssh_host_key() can return one of three values: - * - * - +1 means `key was OK' (either already known or the user just - * approved it) `so continue with the connection' - * - * - 0 means `key was not OK, abandon the connection' - * - * - -1 means `I've initiated enquiries, please wait to be called - * back via the provided function with a result that's either 0 - * or +1'. - */ -int verify_ssh_host_key(void *frontend, char *host, int port, - const char *keytype, char *keystr, char *fingerprint, - void (*callback)(void *ctx, int result), void *ctx); /* * have_ssh_host_key() just returns true if a key of that type is * already cached and false otherwise. */ -int have_ssh_host_key(const char *host, int port, const char *keytype); -/* - * askalg and askhk have the same set of return values as - * verify_ssh_host_key. - * - * (askhk is used in the case where we're using a host key below the - * warning threshold because that's all we have cached, but at least - * one acceptable algorithm is available that we don't have cached.) - */ -int askalg(void *frontend, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx); -int askhk(void *frontend, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx); -/* - * askappend can return four values: - * - * - 2 means overwrite the log file - * - 1 means append to the log file - * - 0 means cancel logging for this session - * - -1 means please wait. - */ -int askappend(void *frontend, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx); +bool have_ssh_host_key(const char *host, int port, const char *keytype); /* * Exports from console frontends (wincons.c, uxcons.c) * that aren't equivalents to things in windlg.c et al. */ -extern int console_batch_mode; -int console_get_userpass_input(prompts_t *p, const unsigned char *in, - int inlen); -void console_provide_logctx(void *logctx); -int is_interactive(void); +extern bool console_batch_mode; +int 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( + const char *prefix, const char *fmt, va_list ap); +void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...); /* * Exports from printing.c. @@ -1319,9 +1825,15 @@ 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, const unsigned char *in, int inlen); +int cmdline_get_passwd_input(prompts_t *p); +bool cmdline_host_ok(Conf *); #define TOOLTYPE_FILETRANSFER 1 #define TOOLTYPE_NONNETWORK 2 +#define TOOLTYPE_HOST_ARG 4 +#define TOOLTYPE_HOST_ARG_CAN_BE_SESSION 8 +#define TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX 16 +#define TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD 32 +#define TOOLTYPE_PORT_ARG 64 extern int cmdline_tooltype; void cmdline_error(const char *, ...); @@ -1331,18 +1843,21 @@ void cmdline_error(const char *, ...); */ struct controlbox; union control; -void conf_radiobutton_handler(union control *ctrl, void *dlg, +void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, void *data, int event); #define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(union control *ctrl, void *dlg, +void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event); -void conf_editbox_handler(union control *ctrl, void *dlg, +void conf_editbox_handler(union control *ctrl, dlgparam *dlg, void *data, int event); -void conf_filesel_handler(union control *ctrl, void *dlg, +void conf_filesel_handler(union control *ctrl, dlgparam *dlg, void *data, int event); -void conf_fontsel_handler(union control *ctrl, void *dlg, +void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, void *data, int event); -void setup_config_box(struct controlbox *b, int midsession, +/* Much more special-purpose function needed by sercfg.c */ +void config_protocolbuttons_handler(union control *, dlgparam *, void *, int); + +void setup_config_box(struct controlbox *b, bool midsession, int protocol, int protcfginfo); /* @@ -1354,7 +1869,7 @@ typedef struct bidi_char { } bidi_char; int do_bidi(bidi_char *line, int count); int do_shape(bidi_char *line, bidi_char *to, int count); -int is_rtl(int c); +bool is_rtl(int c); /* * X11 auth mechanisms we know about. @@ -1367,6 +1882,16 @@ enum { }; extern const char *const x11_authnames[]; /* declared in x11fwd.c */ +/* + * An enum for the copy-paste UI action configuration. + */ +enum { + CLIPUI_NONE, /* UI action has no copy/paste effect */ + CLIPUI_IMPLICIT, /* use the default clipboard implicit in mouse actions */ + CLIPUI_EXPLICIT, /* use the default clipboard for explicit Copy/Paste */ + CLIPUI_CUSTOM, /* use a named clipboard (on systems that support it) */ +}; + /* * Miscellaneous exports from the platform-specific code. * @@ -1375,15 +1900,16 @@ extern const char *const x11_authnames[]; /* declared in x11fwd.c */ */ Filename *filename_from_str(const char *string); const char *filename_to_str(const Filename *fn); -int filename_equal(const Filename *f1, const Filename *f2); -int filename_is_null(const Filename *fn); +bool filename_equal(const Filename *f1, const Filename *f2); +bool filename_is_null(const Filename *fn); Filename *filename_copy(const Filename *fn); void filename_free(Filename *fn); -int filename_serialise(const Filename *f, void *data); -Filename *filename_deserialise(void *data, int maxsize, int *used); +void filename_serialise(BinarySink *bs, const Filename *f); +Filename *filename_deserialise(BinarySource *src); char *get_username(void); /* return value needs freeing */ char *get_random_data(int bytes, const char *device); /* used in cmdgen.c */ char filename_char_sanitise(char c); /* rewrite special pathname chars */ +bool open_for_write_would_lose_data(const Filename *fn); /* * Exports and imports from timing.c. @@ -1405,9 +1931,9 @@ char filename_char_sanitise(char c); /* rewrite special pathname chars */ * run_timers() is called from the front end when it has reason to * think some timers have reached their moment, or when it simply * needs to know how long to wait next. We pass it the time we - * think it is. It returns TRUE and places the time when the next + * think it is. It returns true and places the time when the next * timer needs to go off in `next', or alternatively it returns - * FALSE if there are no timers at all pending. + * false if there are no timers at all pending. * * timer_change_notify() must be supplied by the front end; it * notifies the front end that a new timer has been added to the @@ -1478,7 +2004,7 @@ char filename_char_sanitise(char c); /* rewrite special pathname chars */ typedef void (*timer_fn_t)(void *ctx, unsigned long now); unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx); void expire_timer_context(void *ctx); -int run_timers(unsigned long now, unsigned long *next); +bool run_timers(unsigned long now, unsigned long *next); void timer_change_notify(unsigned long next); unsigned long timing_last_clock(void); @@ -1502,15 +2028,37 @@ unsigned long timing_last_clock(void); * actually running it (e.g. so as to put a zero timeout on a select() * call) then it can call toplevel_callback_pending(), which will * return true if at least one callback is in the queue. + * + * run_toplevel_callbacks() returns true if it ran any actual code. + * This can be used as a means of speculatively terminating a select + * 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); -void run_toplevel_callbacks(void); -int toplevel_callback_pending(void); +bool run_toplevel_callbacks(void); +bool toplevel_callback_pending(void); +void delete_callbacks_for_context(void *ctx); + +/* + * Another facility in callback.c deals with 'idempotent' callbacks, + * defined as those which never need to be scheduled again if they are + * already scheduled and have not yet run. (An example would be one + * which, when called, empties a queue of data completely: when data + * is added to the queue, you must ensure a run of the queue-consuming + * function has been scheduled, but if one is already pending, you + * don't need to schedule a second one.) + */ +struct IdempotentCallback { + toplevel_callback_fn_t fn; + void *ctx; + bool queued; +}; +void queue_idempotent_callback(struct IdempotentCallback *ic); -typedef void (*toplevel_callback_notify_fn_t)(void *frontend); +typedef void (*toplevel_callback_notify_fn_t)(void *ctx); void request_callback_notifications(toplevel_callback_notify_fn_t notify, - void *frontend); + void *ctx); /* * Define no-op macros for the jump list functions, on platforms that diff --git a/puttymem.h b/puttymem.h index 941aded3..b927f1ba 100644 --- a/puttymem.h +++ b/puttymem.h @@ -49,4 +49,19 @@ void safefree(void *); ((type *)snrealloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ (n), sizeof(type))) +/* + * For cases where you want to allocate a struct plus a subsidiary + * data buffer in one step, this macro lets you add a constant to the + * amount malloced. + * + * Since the return value is already cast to the struct type, a + * pointer to that many bytes of extra data can be conveniently + * obtained by simply adding 1 to the returned pointer! + * snew_plus_get_aux is a handy macro that does that and casts the + * result to void *, so you can assign it straight to wherever you + * wanted it. + */ +#define snew_plus(type, extra) ((type *)snmalloc(1, sizeof(type) + (extra))) +#define snew_plus_get_aux(ptr) ((void *)((ptr) + 1)) + #endif diff --git a/raw.c b/raw.c index 0c5445ad..90dde5d8 100644 --- a/raw.c +++ b/raw.c @@ -8,45 +8,40 @@ #include "putty.h" -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - #define RAW_MAX_BACKLOG 4096 -typedef struct raw_backend_data { - const struct plug_function_table *fn; - /* the above field _must_ be first in the structure */ - - Socket s; - int closed_on_socket_error; +typedef struct Raw Raw; +struct Raw { + Socket *s; + bool closed_on_socket_error; int bufsize; - void *frontend; - int sent_console_eof, sent_socket_eof, session_started; + Seat *seat; + LogContext *logctx; + bool sent_console_eof, sent_socket_eof, session_started; Conf *conf; -} *Raw; -static void raw_size(void *handle, int width, int height); + Plug plug; + Backend backend; +}; + +static void raw_size(Backend *be, int width, int height); -static void c_write(Raw raw, char *buf, int len) +static void c_write(Raw *raw, const void *buf, int len) { - int backlog = from_backend(raw->frontend, 0, buf, len); + int backlog = seat_stdout(raw->seat, buf, len); sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); } -static void raw_log(Plug plug, int type, SockAddr addr, int port, +static void raw_log(Plug *plug, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { - Raw raw = (Raw) plug; - backend_socket_log(raw->frontend, type, addr, 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->session_started); } -static void raw_check_close(Raw raw) +static void raw_check_close(Raw *raw) { /* * Called after we send EOF on either the socket or the console. @@ -56,29 +51,29 @@ static void raw_check_close(Raw raw) if (raw->s) { sk_close(raw->s); raw->s = NULL; - notify_remote_exit(raw->frontend); + seat_notify_remote_exit(raw->seat); } } } -static int raw_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void raw_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { - Raw raw = (Raw) plug; + 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; - notify_remote_exit(raw->frontend); + raw->closed_on_socket_error = true; + seat_notify_remote_exit(raw->seat); } - logevent(raw->frontend, error_msg); - connection_fatal(raw->frontend, "%s", error_msg); + 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 && from_backend_eof(raw->frontend)) { + 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. @@ -86,31 +81,36 @@ static int raw_closing(Plug plug, const char *error_msg, int error_code, if (!raw->sent_socket_eof) { if (raw->s) sk_write_eof(raw->s); - raw->sent_socket_eof= TRUE; + raw->sent_socket_eof= true; } } - raw->sent_console_eof = TRUE; + raw->sent_console_eof = true; raw_check_close(raw); } - return 0; } -static int raw_receive(Plug plug, int urgent, char *data, int len) +static void raw_receive(Plug *plug, int urgent, char *data, int len) { - Raw raw = (Raw) plug; + 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; - return 1; + raw->session_started = true; } -static void raw_sent(Plug plug, int bufsize) +static void raw_sent(Plug *plug, int bufsize) { - Raw raw = (Raw) plug; + Raw *raw = container_of(plug, Raw, plug); raw->bufsize = bufsize; } +static const PlugVtable Raw_plugvt = { + raw_log, + raw_closing, + raw_receive, + raw_sent +}; + /* * Called to set up the raw connection. * @@ -119,41 +119,37 @@ static void raw_sent(Plug plug, int bufsize) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *raw_init(void *frontend_handle, void **backend_handle, - Conf *conf, +static const char *raw_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, const char *host, int port, char **realhost, - int nodelay, int keepalive) + bool nodelay, bool keepalive) { - static const struct plug_function_table fn_table = { - raw_log, - raw_closing, - raw_receive, - raw_sent - }; - SockAddr addr; + SockAddr *addr; const char *err; - Raw raw; + Raw *raw; int addressfamily; char *loghost; - raw = snew(struct raw_backend_data); - raw->fn = &fn_table; + raw = snew(Raw); + raw->plug.vt = &Raw_plugvt; + raw->backend.vt = &raw_backend; raw->s = NULL; - raw->closed_on_socket_error = FALSE; - *backend_handle = raw; - raw->sent_console_eof = raw->sent_socket_eof = FALSE; + 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->session_started = false; raw->conf = conf_copy(conf); - raw->frontend = frontend_handle; + 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->frontend, "main connection"); + raw->logctx, "main connection"); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; @@ -165,8 +161,8 @@ static const char *raw_init(void *frontend_handle, void **backend_handle, /* * Open socket. */ - raw->s = new_connection(addr, *realhost, port, 0, 1, nodelay, keepalive, - (Plug) raw, conf); + raw->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &raw->plug, conf); if ((err = sk_socket_error(raw->s)) != NULL) return err; @@ -185,9 +181,9 @@ static const char *raw_init(void *frontend_handle, void **backend_handle, return NULL; } -static void raw_free(void *handle) +static void raw_free(Backend *be) { - Raw raw = (Raw) handle; + Raw *raw = container_of(be, Raw, backend); if (raw->s) sk_close(raw->s); @@ -198,16 +194,16 @@ static void raw_free(void *handle) /* * Stub routine (we don't have any need to reconfigure this backend). */ -static void raw_reconfig(void *handle, Conf *conf) +static void raw_reconfig(Backend *be, Conf *conf) { } /* * Called to send data down the raw connection. */ -static int raw_send(void *handle, const char *buf, int len) +static int raw_send(Backend *be, const char *buf, int len) { - Raw raw = (Raw) handle; + Raw *raw = container_of(be, Raw, backend); if (raw->s == NULL) return 0; @@ -220,16 +216,16 @@ static int raw_send(void *handle, const char *buf, int len) /* * Called to query the current socket sendability status. */ -static int raw_sendbuffer(void *handle) +static int raw_sendbuffer(Backend *be) { - Raw raw = (Raw) handle; + Raw *raw = container_of(be, Raw, backend); return raw->bufsize; } /* * Called to set the size of the window */ -static void raw_size(void *handle, int width, int height) +static void raw_size(Backend *be, int width, int height) { /* Do nothing! */ return; @@ -238,12 +234,12 @@ static void raw_size(void *handle, int width, int height) /* * Send raw special codes. We only handle outgoing EOF here. */ -static void raw_special(void *handle, Telnet_Special code) +static void raw_special(Backend *be, SessionSpecialCode code, int arg) { - Raw raw = (Raw) handle; - if (code == TS_EOF && raw->s) { + Raw *raw = container_of(be, Raw, backend); + if (code == SS_EOF && raw->s) { sk_write_eof(raw->s); - raw->sent_socket_eof= TRUE; + raw->sent_socket_eof= true; raw_check_close(raw); } @@ -254,48 +250,43 @@ static void raw_special(void *handle, Telnet_Special code) * Return a list of the special codes that make sense in this * protocol. */ -static const struct telnet_special *raw_get_specials(void *handle) +static const SessionSpecial *raw_get_specials(Backend *be) { return NULL; } -static int raw_connected(void *handle) +static bool raw_connected(Backend *be) { - Raw raw = (Raw) handle; + Raw *raw = container_of(be, Raw, backend); return raw->s != NULL; } -static int raw_sendok(void *handle) +static bool raw_sendok(Backend *be) { - return 1; + return true; } -static void raw_unthrottle(void *handle, int backlog) +static void raw_unthrottle(Backend *be, int backlog) { - Raw raw = (Raw) handle; + Raw *raw = container_of(be, Raw, backend); sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); } -static int raw_ldisc(void *handle, int option) +static bool raw_ldisc(Backend *be, int option) { if (option == LD_EDIT || option == LD_ECHO) - return 1; - return 0; -} - -static void raw_provide_ldisc(void *handle, void *ldisc) -{ - /* This is a stub. */ + return true; + return false; } -static void raw_provide_logctx(void *handle, void *logctx) +static void raw_provide_ldisc(Backend *be, Ldisc *ldisc) { /* This is a stub. */ } -static int raw_exitcode(void *handle) +static int raw_exitcode(Backend *be) { - Raw raw = (Raw) handle; + Raw *raw = container_of(be, Raw, backend); if (raw->s != NULL) return -1; /* still connected */ else if (raw->closed_on_socket_error) @@ -308,12 +299,12 @@ static int raw_exitcode(void *handle) /* * cfg_info for Raw does nothing at all. */ -static int raw_cfg_info(void *handle) +static int raw_cfg_info(Backend *be) { return 0; } -Backend raw_backend = { +const struct BackendVtable raw_backend = { raw_init, raw_free, raw_reconfig, @@ -327,7 +318,6 @@ Backend raw_backend = { raw_sendok, raw_ldisc, raw_provide_ldisc, - raw_provide_logctx, raw_unthrottle, raw_cfg_info, NULL /* test_for_upstream */, diff --git a/release.pl b/release.pl index cf73d9eb..b5ad149c 100755 --- a/release.pl +++ b/release.pl @@ -15,11 +15,13 @@ 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) + "postcheck" => \$postcheck, + "no-ftp" => \$skip_ftp) or &usage(); # --set-version: construct a local commit which updates the version @@ -29,10 +31,12 @@ 0 == system "git", "diff-index", "--quiet", "--cached", "HEAD" or die "index is dirty"; 0 == system "git", "diff-files", "--quiet" or die "working tree is dirty"; - -f "Makefile" and die "run 'make distclean' first"; my $builddir = tempdir(DIR => ".", CLEANUP => 1); - 0 == system "./mkfiles.pl" or die; - 0 == system "cd $builddir && ../configure" or die; + 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; our $pscp_transcript = `cd $builddir && ./pscp --help`; $pscp_transcript =~ s/^Unidentified build/Release ${version}/m or die; @@ -161,11 +165,13 @@ } # Now test-download the files themselves. - 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"; + 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); diff --git a/rlogin.c b/rlogin.c index eba468da..46bfb7eb 100644 --- a/rlogin.c +++ b/rlogin.c @@ -9,54 +9,47 @@ #include "putty.h" -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - #define RLOGIN_MAX_BACKLOG 4096 -typedef struct rlogin_tag { - const struct plug_function_table *fn; - /* the above field _must_ be first in the structure */ - - Socket s; - int closed_on_socket_error; +typedef struct Rlogin Rlogin; +struct Rlogin { + Socket *s; + bool closed_on_socket_error; int bufsize; - int firstbyte; - int cansize; + bool firstbyte; + bool cansize; int term_width, term_height; - void *frontend; + Seat *seat; + LogContext *logctx; Conf *conf; /* In case we need to read a username from the terminal before starting */ prompts_t *prompt; -} *Rlogin; -static void rlogin_size(void *handle, int width, int height); + Plug plug; + Backend backend; +}; -static void c_write(Rlogin rlogin, char *buf, int len) +static void c_write(Rlogin *rlogin, const void *buf, int len) { - int backlog = from_backend(rlogin->frontend, 0, buf, len); + int backlog = seat_stdout(rlogin->seat, buf, len); sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); } -static void rlogin_log(Plug plug, int type, SockAddr addr, int port, +static void rlogin_log(Plug *plug, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { - Rlogin rlogin = (Rlogin) plug; - backend_socket_log(rlogin->frontend, type, addr, 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); } -static int rlogin_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { - Rlogin rlogin = (Rlogin) plug; + Rlogin *rlogin = container_of(plug, Rlogin, plug); /* * We don't implement independent EOF in each direction for Telnet @@ -68,28 +61,28 @@ static int rlogin_closing(Plug plug, const char *error_msg, int error_code, sk_close(rlogin->s); rlogin->s = NULL; if (error_msg) - rlogin->closed_on_socket_error = TRUE; - notify_remote_exit(rlogin->frontend); + rlogin->closed_on_socket_error = true; + seat_notify_remote_exit(rlogin->seat); } if (error_msg) { /* A socket error has occurred. */ - logevent(rlogin->frontend, error_msg); - connection_fatal(rlogin->frontend, "%s", error_msg); + logevent(rlogin->logctx, error_msg); + seat_connection_fatal(rlogin->seat, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ - return 0; } -static int rlogin_receive(Plug plug, int urgent, char *data, int len) +static void rlogin_receive(Plug *plug, int urgent, char *data, int len) { - Rlogin rlogin = (Rlogin) plug; + Rlogin *rlogin = container_of(plug, Rlogin, plug); if (urgent == 2) { char c; c = *data++; len--; if (c == '\x80') { - rlogin->cansize = 1; - rlogin_size(rlogin, rlogin->term_width, rlogin->term_height); + rlogin->cansize = true; + backend_size(&rlogin->backend, + rlogin->term_width, rlogin->term_height); } /* * We should flush everything (aka Telnet SYNCH) if we see @@ -108,21 +101,20 @@ static int rlogin_receive(Plug plug, int urgent, char *data, int len) data++; len--; } - rlogin->firstbyte = 0; + rlogin->firstbyte = false; } if (len > 0) c_write(rlogin, data, len); } - return 1; } -static void rlogin_sent(Plug plug, int bufsize) +static void rlogin_sent(Plug *plug, int bufsize) { - Rlogin rlogin = (Rlogin) plug; + Rlogin *rlogin = container_of(plug, Rlogin, plug); rlogin->bufsize = bufsize; } -static void rlogin_startup(Rlogin rlogin, const char *ruser) +static void rlogin_startup(Rlogin *rlogin, const char *ruser) { char z = 0; char *p; @@ -143,6 +135,13 @@ static void rlogin_startup(Rlogin rlogin, const char *ruser) rlogin->prompt = NULL; } +static const PlugVtable Rlogin_plugvt = { + rlogin_log, + rlogin_closing, + rlogin_receive, + rlogin_sent +}; + /* * Called to set up the rlogin connection. * @@ -151,43 +150,39 @@ static void rlogin_startup(Rlogin rlogin, const char *ruser) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *rlogin_init(void *frontend_handle, void **backend_handle, - Conf *conf, +static const char *rlogin_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, const char *host, int port, char **realhost, - int nodelay, int keepalive) + bool nodelay, bool keepalive) { - static const struct plug_function_table fn_table = { - rlogin_log, - rlogin_closing, - rlogin_receive, - rlogin_sent - }; - SockAddr addr; + SockAddr *addr; const char *err; - Rlogin rlogin; + Rlogin *rlogin; char *ruser; int addressfamily; char *loghost; - rlogin = snew(struct rlogin_tag); - rlogin->fn = &fn_table; + rlogin = snew(Rlogin); + rlogin->plug.vt = &Rlogin_plugvt; + rlogin->backend.vt = &rlogin_backend; rlogin->s = NULL; - rlogin->closed_on_socket_error = FALSE; - rlogin->frontend = frontend_handle; + 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 = 1; - rlogin->cansize = 0; + rlogin->firstbyte = true; + rlogin->cansize = false; rlogin->prompt = NULL; rlogin->conf = conf_copy(conf); - *backend_handle = rlogin; + *backend_handle = &rlogin->backend; addressfamily = conf_get_int(conf, CONF_addressfamily); /* * Try to find host. */ addr = name_lookup(host, port, realhost, conf, addressfamily, - rlogin->frontend, "rlogin connection"); + rlogin->logctx, "rlogin connection"); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; @@ -199,8 +194,8 @@ static const char *rlogin_init(void *frontend_handle, void **backend_handle, /* * Open socket. */ - rlogin->s = new_connection(addr, *realhost, port, 1, 0, - nodelay, keepalive, (Plug) rlogin, conf); + rlogin->s = new_connection(addr, *realhost, port, true, false, + nodelay, keepalive, &rlogin->plug, conf); if ((err = sk_socket_error(rlogin->s)) != NULL) return err; @@ -228,11 +223,11 @@ static const char *rlogin_init(void *frontend_handle, void **backend_handle, } else { int ret; - rlogin->prompt = new_prompts(rlogin->frontend); - rlogin->prompt->to_server = TRUE; + rlogin->prompt = new_prompts(); + rlogin->prompt->to_server = true; rlogin->prompt->name = dupstr("Rlogin login name"); - add_prompt(rlogin->prompt, dupstr("rlogin username: "), TRUE); - ret = get_userpass_input(rlogin->prompt, NULL, 0); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); + ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); if (ret >= 0) { rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); } @@ -241,9 +236,9 @@ static const char *rlogin_init(void *frontend_handle, void **backend_handle, return NULL; } -static void rlogin_free(void *handle) +static void rlogin_free(Backend *be) { - Rlogin rlogin = (Rlogin) handle; + Rlogin *rlogin = container_of(be, Rlogin, backend); if (rlogin->prompt) free_prompts(rlogin->prompt); @@ -256,54 +251,67 @@ static void rlogin_free(void *handle) /* * Stub routine (we don't have any need to reconfigure this backend). */ -static void rlogin_reconfig(void *handle, Conf *conf) +static void rlogin_reconfig(Backend *be, Conf *conf) { } /* * Called to send data down the rlogin connection. */ -static int rlogin_send(void *handle, const char *buf, int len) +static int rlogin_send(Backend *be, const char *buf, int len) { - Rlogin rlogin = (Rlogin) handle; + 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 = get_userpass_input(rlogin->prompt, - (unsigned char *)buf, len); + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); if (ret >= 0) { rlogin_startup(rlogin, rlogin->prompt->prompts[0]->result); /* that nulls out rlogin->prompt, so then we'll start sending * data down the wire in the obvious way */ } - } else { - rlogin->bufsize = sk_write(rlogin->s, buf, len); } + if (!rlogin->prompt) { + while (bufchain_size(&bc) > 0) { + void *data; + int len; + bufchain_prefix(&bc, &data, &len); + rlogin->bufsize = sk_write(rlogin->s, data, len); + bufchain_consume(&bc, len); + } + } + + bufchain_clear(&bc); + return rlogin->bufsize; } /* * Called to query the current socket sendability status. */ -static int rlogin_sendbuffer(void *handle) +static int rlogin_sendbuffer(Backend *be) { - Rlogin rlogin = (Rlogin) handle; + Rlogin *rlogin = container_of(be, Rlogin, backend); return rlogin->bufsize; } /* * Called to set the size of the window */ -static void rlogin_size(void *handle, int width, int height) +static void rlogin_size(Backend *be, int width, int height) { - Rlogin rlogin = (Rlogin) handle; + 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; @@ -323,7 +331,7 @@ static void rlogin_size(void *handle, int width, int height) /* * Send rlogin special codes. */ -static void rlogin_special(void *handle, Telnet_Special code) +static void rlogin_special(Backend *be, SessionSpecialCode code, int arg) { /* Do nothing! */ return; @@ -333,48 +341,43 @@ static void rlogin_special(void *handle, Telnet_Special code) * Return a list of the special codes that make sense in this * protocol. */ -static const struct telnet_special *rlogin_get_specials(void *handle) +static const SessionSpecial *rlogin_get_specials(Backend *be) { return NULL; } -static int rlogin_connected(void *handle) +static bool rlogin_connected(Backend *be) { - Rlogin rlogin = (Rlogin) handle; + Rlogin *rlogin = container_of(be, Rlogin, backend); return rlogin->s != NULL; } -static int rlogin_sendok(void *handle) +static bool rlogin_sendok(Backend *be) { - /* Rlogin rlogin = (Rlogin) handle; */ - return 1; + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return true; } -static void rlogin_unthrottle(void *handle, int backlog) +static void rlogin_unthrottle(Backend *be, int backlog) { - Rlogin rlogin = (Rlogin) handle; + Rlogin *rlogin = container_of(be, Rlogin, backend); sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); } -static int rlogin_ldisc(void *handle, int option) -{ - /* Rlogin rlogin = (Rlogin) handle; */ - return 0; -} - -static void rlogin_provide_ldisc(void *handle, void *ldisc) +static bool rlogin_ldisc(Backend *be, int option) { - /* This is a stub. */ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return false; } -static void rlogin_provide_logctx(void *handle, void *logctx) +static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) { /* This is a stub. */ } -static int rlogin_exitcode(void *handle) +static int rlogin_exitcode(Backend *be) { - Rlogin rlogin = (Rlogin) handle; + Rlogin *rlogin = container_of(be, Rlogin, backend); if (rlogin->s != NULL) return -1; /* still connected */ else if (rlogin->closed_on_socket_error) @@ -387,12 +390,12 @@ static int rlogin_exitcode(void *handle) /* * cfg_info for rlogin does nothing at all. */ -static int rlogin_cfg_info(void *handle) +static int rlogin_cfg_info(Backend *be) { return 0; } -Backend rlogin_backend = { +const struct BackendVtable rlogin_backend = { rlogin_init, rlogin_free, rlogin_reconfig, @@ -406,7 +409,6 @@ Backend rlogin_backend = { rlogin_sendok, rlogin_ldisc, rlogin_provide_ldisc, - rlogin_provide_logctx, rlogin_unthrottle, rlogin_cfg_info, NULL /* test_for_upstream */, diff --git a/scpserver.c b/scpserver.c new file mode 100644 index 00000000..42439159 --- /dev/null +++ b/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 "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 struct SftpReplyBuilderVtable ScpReplyReceiver_vt = { + scp_reply_ok, + scp_reply_error, + scp_reply_simple_name, + scp_reply_name_count, + scp_reply_full_name, + scp_reply_handle, + scp_reply_data, + 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 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 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 int 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 struct ScpServerVtable ScpSource_ScpServer_vt = { + scp_source_free, + scp_source_send, + scp_source_throttle, + 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); + } + 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 int 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 int 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 struct ScpServerVtable ScpSink_ScpServer_vt = { + scp_sink_free, + scp_sink_send, + scp_sink_throttle, + 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); + scp->command->len = 0; + while (1) { + crMaybeWaitUntilV(scp->input_eof || bufchain_size(&scp->data) > 0); + if (scp->input_eof) + goto done; + + void *vdata; + int len; + const char *cdata, *newline; + + bufchain_prefix(&scp->data, &vdata, &len); + cdata = vdata; + newline = memchr(cdata, '\012', len); + if (newline) + len = (int)(newline+1 - cdata); + put_data(scp->command, cdata, len); + bufchain_consume(&scp->data, len); + + if (newline) + break; + } + + /* + * Parse the command. + */ + scp->command->len--; /* chomp the newline */ + 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)); + scp->filename_sb->len = 0; + put_data(scp->filename_sb, scp->head->destpath.ptr, + scp->head->destpath.len); + 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_data(scp->filename_sb, leafname.ptr, leafname.len); + } + 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) { + void *vdata; + int len; + 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; + } + + bufchain_prefix(&scp->data, &vdata, &len); + this_len = 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(vdata, 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 int 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 int 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 struct ScpServerVtable ScpError_ScpServer_vt = { + scp_error_free, + scp_error_send, + scp_error_throttle, + 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 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/sercfg.c b/sercfg.c index fef910f3..a5a3e3f4 100644 --- a/sercfg.c +++ b/sercfg.c @@ -16,7 +16,7 @@ #include "dialog.h" #include "storage.h" -static void serial_parity_handler(union control *ctrl, void *dlg, +static void serial_parity_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { static const struct { @@ -71,7 +71,7 @@ static void serial_parity_handler(union control *ctrl, void *dlg, } } -static void serial_flow_handler(union control *ctrl, void *dlg, +static void serial_flow_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { static const struct { @@ -124,7 +124,7 @@ static void serial_flow_handler(union control *ctrl, void *dlg, } } -void ser_setup_config_box(struct controlbox *b, int midsession, +void ser_setup_config_box(struct controlbox *b, bool midsession, int parity_mask, int flow_mask) { struct controlset *s; @@ -132,8 +132,6 @@ void ser_setup_config_box(struct controlbox *b, int midsession, if (!midsession) { int i; - extern void config_protocolbuttons_handler(union control *, void *, - void *, int); /* * Add the serial back end to the protocols list at the diff --git a/sesschan.c b/sesschan.c new file mode 100644 index 00000000..1ca085d1 --- /dev/null +++ b/sesschan.c @@ -0,0 +1,720 @@ +/* + * 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" + +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]; + + Plug agentfwd_plug; + Socket *agentfwd_socket; + + Backend *backend; + + bufchain subsys_input; + SftpServer *sftpsrv; + ScpServer *scpsrv; + + Channel chan; +} sesschan; + +static void sesschan_free(Channel *chan); +static int sesschan_send(Channel *chan, bool is_stderr, const void *, int); +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 struct ChannelVtable sesschan_channelvt = { + sesschan_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + sesschan_send, + sesschan_send_eof, + sesschan_set_input_wanted, + sesschan_log_close_msg, + sesschan_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + sesschan_run_shell, + sesschan_run_command, + sesschan_run_subsystem, + sesschan_enable_x11_forwarding, + sesschan_enable_agent_forwarding, + sesschan_allocate_pty, + sesschan_set_env, + sesschan_send_break, + sesschan_send_signal, + sesschan_change_window_size, + chan_no_request_response, +}; + +static int sftp_chan_send(Channel *chan, bool is_stderr, const void *, int); +static void sftp_chan_send_eof(Channel *chan); +static char *sftp_log_close_msg(Channel *chan); + +static const struct ChannelVtable sftp_channelvt = { + sesschan_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + sftp_chan_send, + sftp_chan_send_eof, + sesschan_set_input_wanted, + sftp_log_close_msg, + chan_default_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + chan_no_request_response, +}; + +static int scp_chan_send(Channel *chan, bool is_stderr, const void *, int); +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 struct ChannelVtable scp_channelvt = { + sesschan_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + scp_chan_send, + scp_chan_send_eof, + scp_set_input_wanted, + scp_log_close_msg, + chan_default_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + 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 = { + sesschan_eventlog, + sesschan_askappend, + sesschan_logging_error, +}; + +static int sesschan_seat_output(Seat *, bool is_stderr, const void *, int); +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 = { + sesschan_seat_output, + sesschan_seat_eof, + nullseat_get_userpass_input, + sesschan_notify_remote_exit, + sesschan_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + nullseat_verify_ssh_host_key, + nullseat_confirm_weak_crypto_primitive, + nullseat_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + sesschan_get_window_pixel_size, +}; + +Channel *sesschan_new(SshChannel *c, LogContext *logctx, + const SftpServerVtable *sftpserver_vt) +{ + 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; + + /* 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->agentfwd_socket) + sk_close(sess->agentfwd_socket); + + sfree(sess); +} + +static int sesschan_send(Channel *chan, bool is_stderr, + const void *data, int 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) +{ + sess->backend = pty_backend_create( + &sess->seat, sess->child_logctx, sess->conf, NULL, cmd, + sess->ttymodes, !sess->want_pty); + 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, int 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); + 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 = { + fwd_log, + fwd_closing, + NULL, /* recv */ + NULL, /* send */ + 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; + 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. + */ + if (authdata_hex.len % 2) + return false; /* expected an even number of digits */ + authdata_bin = strbuf_new(); + 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; + + 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); + + strbuf_free(authdata_bin); + return sess->n_x11_sockets != 0; +} + +static int agentfwd_accepting( + Plug *p, accept_fn_t constructor, accept_ctx_t ctx) +{ + sesschan *sess = container_of(p, sesschan, agentfwd_plug); + Plug *plug; + Channel *chan; + Socket *s; + const char *err; + + chan = portfwd_raw_new(sess->c->cl, &plug); + 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(sess->c->cl, chan)); + + return 0; +} + +static const PlugVtable agentfwd_plugvt = { + fwd_log, + fwd_closing, + NULL, /* recv */ + NULL, /* send */ + agentfwd_accepting, +}; + +bool sesschan_enable_agent_forwarding(Channel *chan) +{ + sesschan *sess = container_of(chan, sesschan, chan); + char *error, *socketname, *dir_prefix; + + dir_prefix = dupprintf("/tmp/%s-agentfwd", appname); + + sess->agentfwd_plug.vt = &agentfwd_plugvt; + sess->agentfwd_socket = platform_make_agent_socket( + &sess->agentfwd_plug, dir_prefix, &error, &socketname); + + sfree(dir_prefix); + + if (sess->agentfwd_socket) { + conf_set_str_str(sess->conf, CONF_environmt, + "SSH_AUTH_SOCK", socketname); + } + + sfree(error); + sfree(socketname); + + return sess->agentfwd_socket != NULL; +} + +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 int sesschan_seat_output( + Seat *seat, bool is_stderr, const void *data, int 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); + ptrlen signame; + char *sigmsg; + + if (!sess->backend) + return; + + 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)); + + sfree(sigmsg); + } else { + 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 int sftp_chan_send(Channel *chan, bool is_stderr, + const void *data, int 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(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 int scp_chan_send(Channel *chan, bool is_stderr, + const void *data, int 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/sessprep.c b/sessprep.c new file mode 100644 index 00000000..15d830d9 --- /dev/null +++ b/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/settings.c b/settings.c index f810d3f9..5c3f534a 100644 --- a/settings.c +++ b/settings.c @@ -7,6 +7,11 @@ #include #include "putty.h" #include "storage.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + /* The cipher order given here is the default order. */ static const struct keyvalwhere ciphernames[] = { @@ -69,18 +74,18 @@ const char *const ttymodes[] = { * (which is only present in tools that manage settings). */ -Backend *backend_from_name(const char *name) +const struct BackendVtable *backend_vt_from_name(const char *name) { - Backend **p; + const struct BackendVtable *const *p; for (p = backends; *p != NULL; p++) if (!strcmp((*p)->name, name)) return *p; return NULL; } -Backend *backend_from_proto(int proto) +const struct BackendVtable *backend_vt_from_proto(int proto) { - Backend **p; + const struct BackendVtable *const *p; for (p = backends; *p != NULL; p++) if ((*p)->protocol == proto) return *p; @@ -92,7 +97,7 @@ char *get_remote_username(Conf *conf) char *username = conf_get_str(conf, CONF_username); if (*username) { return dupstr(username); - } else if (conf_get_int(conf, CONF_username_from_env)) { + } else if (conf_get_bool(conf, CONF_username_from_env)) { /* Use local username. */ return get_username(); /* might still be NULL */ } else { @@ -100,9 +105,9 @@ char *get_remote_username(Conf *conf) } } -static char *gpps_raw(void *handle, const char *name, const char *def) +static char *gpps_raw(settings_r *sesskey, const char *name, const char *def) { - char *ret = read_setting_s(handle, name); + char *ret = sesskey ? read_setting_s(sesskey, name) : NULL; if (!ret) ret = platform_default_s(name); if (!ret) @@ -110,10 +115,10 @@ static char *gpps_raw(void *handle, const char *name, const char *def) return ret; } -static void gpps(void *handle, const char *name, const char *def, +static void gpps(settings_r *sesskey, const char *name, const char *def, Conf *conf, int primary) { - char *val = gpps_raw(handle, name, def); + char *val = gpps_raw(sesskey, name, def); conf_set_str(conf, primary, val); sfree(val); } @@ -123,33 +128,47 @@ static void gpps(void *handle, const char *name, const char *def, * format of a Filename or FontSpec is platform-dependent. So the * platform-dependent functions MUST return some sort of value. */ -static void gppfont(void *handle, const char *name, Conf *conf, int primary) +static void gppfont(settings_r *sesskey, char *name, + Conf *conf, int primary) { - FontSpec *result = read_setting_fontspec(handle, name); + FontSpec *result = sesskey ? read_setting_fontspec(sesskey, name) : NULL; if (!result) result = platform_default_fontspec(name); conf_set_fontspec(conf, primary, result); fontspec_free(result); } -static void gppfile(void *handle, const char *name, Conf *conf, int primary) +static void gppfile(settings_r *sesskey, const char *name, + Conf *conf, int primary) { - Filename *result = read_setting_filename(handle, name); + Filename *result = sesskey ? read_setting_filename(sesskey, name) : NULL; if (!result) result = platform_default_filename(name); conf_set_filename(conf, primary, result); filename_free(result); } -static int gppi_raw(void *handle, const char *name, int def) +static bool gppb_raw(settings_r *sesskey, const char *name, bool def) +{ + def = platform_default_b(name, def); + return sesskey ? read_setting_i(sesskey, name, def) != 0 : def; +} + +static void gppb(settings_r *sesskey, const char *name, bool def, + Conf *conf, int primary) +{ + conf_set_bool(conf, primary, gppb_raw(sesskey, name, def)); +} + +static int gppi_raw(settings_r *sesskey, const char *name, int def) { def = platform_default_i(name, def); - return read_setting_i(handle, name, def); + return sesskey ? read_setting_i(sesskey, name, def) : def; } -static void gppi(void *handle, const char *name, int def, +static void gppi(settings_r *sesskey, const char *name, int def, Conf *conf, int primary) { - conf_set_int(conf, primary, gppi_raw(handle, name, def)); + conf_set_int(conf, primary, gppi_raw(sesskey, name, def)); } /* @@ -159,7 +178,8 @@ static void gppi(void *handle, const char *name, int def, * If there's no "=VALUE" (e.g. just NAME,NAME,NAME) then those keys * are mapped to the empty string. */ -static int gppmap(void *handle, const char *name, Conf *conf, int primary) +static bool gppmap(settings_r *sesskey, const char *name, + Conf *conf, int primary) { char *buf, *p, *q, *key, *val; @@ -173,9 +193,9 @@ static int gppmap(void *handle, const char *name, Conf *conf, int primary) * Now read a serialised list from the settings and unmarshal it * into its components. */ - buf = gpps_raw(handle, name, NULL); + buf = gpps_raw(sesskey, name, NULL); if (!buf) - return FALSE; + return false; p = buf; while (*p) { @@ -219,15 +239,15 @@ static int gppmap(void *handle, const char *name, Conf *conf, int primary) } sfree(buf); - return TRUE; + return true; } /* * Write a set of name/value pairs in the above format, or just the - * names if include_values is FALSE. + * names if include_values is false. */ -static void wmap(void *handle, char const *outkey, Conf *conf, int primary, - int include_values) +static void wmap(settings_w *sesskey, char const *outkey, Conf *conf, + int primary, bool include_values) { char *buf, *p, *key, *realkey; const char *val, *q; @@ -288,7 +308,7 @@ static void wmap(void *handle, char const *outkey, Conf *conf, int primary, } } *p = '\0'; - write_setting_s(handle, outkey, buf); + write_setting_s(sesskey, outkey, buf); sfree(buf); } @@ -398,7 +418,7 @@ static void gprefs_from_str(const char *str, /* * Read a preference list. */ -static void gprefs(void *sesskey, const char *name, const char *def, +static void gprefs(settings_r *sesskey, const char *name, const char *def, const struct keyvalwhere *mapping, int nvals, Conf *conf, int primary) { @@ -413,7 +433,7 @@ static void gprefs(void *sesskey, const char *name, const char *def, /* * Write out a preference list. */ -static void wprefs(void *sesskey, const char *name, +static void wprefs(settings_w *sesskey, const char *name, const struct keyvalwhere *mapping, int nvals, Conf *conf, int primary) { @@ -447,9 +467,62 @@ static void wprefs(void *sesskey, const char *name, sfree(buf); } +static void write_setting_b(settings_w *handle, const char *key, bool value) +{ + write_setting_i(handle, key, value ? 1 : 0); +} + +static void write_clip_setting(settings_w *sesskey, const char *savekey, + Conf *conf, int confkey, int strconfkey) +{ + int val = conf_get_int(conf, confkey); + switch (val) { + case CLIPUI_NONE: + default: + write_setting_s(sesskey, savekey, "none"); + break; + case CLIPUI_IMPLICIT: + write_setting_s(sesskey, savekey, "implicit"); + break; + case CLIPUI_EXPLICIT: + write_setting_s(sesskey, savekey, "explicit"); + break; + case CLIPUI_CUSTOM: + { + char *sval = dupcat("custom:", conf_get_str(conf, strconfkey), + (const char *)NULL); + write_setting_s(sesskey, savekey, sval); + sfree(sval); + } + break; + } +} + +static void read_clip_setting(settings_r *sesskey, char *savekey, + int def, Conf *conf, int confkey, int strconfkey) +{ + char *setting = read_setting_s(sesskey, savekey); + int val; + + conf_set_str(conf, strconfkey, ""); + if (!setting) { + val = def; + } else if (!strcmp(setting, "implicit")) { + val = CLIPUI_IMPLICIT; + } else if (!strcmp(setting, "explicit")) { + val = CLIPUI_EXPLICIT; + } else if (!strncmp(setting, "custom:", 7)) { + val = CLIPUI_CUSTOM; + conf_set_str(conf, strconfkey, setting + 7); + } else { + val = CLIPUI_NONE; + } + conf_set_int(conf, confkey, val); +} + char *save_settings(const char *section, Conf *conf) { - void *sesskey; + struct settings_w *sesskey; char *errmsg; sesskey = open_settings_w(section, &errmsg); @@ -460,7 +533,7 @@ char *save_settings(const char *section, Conf *conf) return NULL; } -void save_open_settings(void *sesskey, Conf *conf) +void save_open_settings(settings_w *sesskey, Conf *conf) { int i; const char *p; @@ -470,28 +543,30 @@ void save_open_settings(void *sesskey, Conf *conf) write_setting_filename(sesskey, "LogFileName", conf_get_filename(conf, CONF_logfilename)); write_setting_i(sesskey, "LogType", conf_get_int(conf, CONF_logtype)); write_setting_i(sesskey, "LogFileClash", conf_get_int(conf, CONF_logxfovr)); - write_setting_i(sesskey, "LogFlush", conf_get_int(conf, CONF_logflush)); - write_setting_i(sesskey, "SSHLogOmitPasswords", conf_get_int(conf, CONF_logomitpass)); - write_setting_i(sesskey, "SSHLogOmitData", conf_get_int(conf, CONF_logomitdata)); + write_setting_b(sesskey, "LogFlush", conf_get_bool(conf, CONF_logflush)); + write_setting_b(sesskey, "LogHeader", conf_get_bool(conf, CONF_logheader)); + write_setting_b(sesskey, "SSHLogOmitPasswords", conf_get_bool(conf, CONF_logomitpass)); + write_setting_b(sesskey, "SSHLogOmitData", conf_get_bool(conf, CONF_logomitdata)); p = "raw"; { - const Backend *b = backend_from_proto(conf_get_int(conf, CONF_protocol)); - if (b) - p = b->name; + const struct BackendVtable *vt = + backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + if (vt) + p = vt->name; } write_setting_s(sesskey, "Protocol", p); write_setting_i(sesskey, "PortNumber", conf_get_int(conf, CONF_port)); /* The CloseOnExit numbers are arranged in a different order from * the standard FORCE_ON / FORCE_OFF / AUTO. */ write_setting_i(sesskey, "CloseOnExit", (conf_get_int(conf, CONF_close_on_exit)+2)%3); - write_setting_i(sesskey, "WarnOnClose", !!conf_get_int(conf, CONF_warn_on_close)); + write_setting_b(sesskey, "WarnOnClose", !!conf_get_bool(conf, CONF_warn_on_close)); write_setting_i(sesskey, "PingInterval", conf_get_int(conf, CONF_ping_interval) / 60); /* minutes */ write_setting_i(sesskey, "PingIntervalSecs", conf_get_int(conf, CONF_ping_interval) % 60); /* seconds */ - write_setting_i(sesskey, "TCPNoDelay", conf_get_int(conf, CONF_tcp_nodelay)); - write_setting_i(sesskey, "TCPKeepalives", conf_get_int(conf, CONF_tcp_keepalives)); + write_setting_b(sesskey, "TCPNoDelay", conf_get_bool(conf, CONF_tcp_nodelay)); + write_setting_b(sesskey, "TCPKeepalives", conf_get_bool(conf, CONF_tcp_keepalives)); write_setting_s(sesskey, "TerminalType", conf_get_str(conf, CONF_termtype)); write_setting_s(sesskey, "TerminalSpeed", conf_get_str(conf, CONF_termspeed)); - wmap(sesskey, "TerminalModes", conf, CONF_ttymodes, TRUE); + wmap(sesskey, "TerminalModes", conf, CONF_ttymodes, true); /* Address family selection */ write_setting_i(sesskey, "AddressFamily", conf_get_int(conf, CONF_addressfamily)); @@ -499,7 +574,7 @@ void save_open_settings(void *sesskey, Conf *conf) /* proxy settings */ write_setting_s(sesskey, "ProxyExcludeList", conf_get_str(conf, CONF_proxy_exclude_list)); write_setting_i(sesskey, "ProxyDNS", (conf_get_int(conf, CONF_proxy_dns)+2)%3); - write_setting_i(sesskey, "ProxyLocalhost", conf_get_int(conf, CONF_even_proxy_localhost)); + write_setting_b(sesskey, "ProxyLocalhost", conf_get_bool(conf, CONF_even_proxy_localhost)); write_setting_i(sesskey, "ProxyMethod", conf_get_int(conf, CONF_proxy_type)); write_setting_s(sesskey, "ProxyHost", conf_get_str(conf, CONF_proxy_host)); write_setting_i(sesskey, "ProxyPort", conf_get_int(conf, CONF_proxy_port)); @@ -507,79 +582,81 @@ void save_open_settings(void *sesskey, Conf *conf) write_setting_s(sesskey, "ProxyPassword", conf_get_str(conf, CONF_proxy_password)); write_setting_s(sesskey, "ProxyTelnetCommand", conf_get_str(conf, CONF_proxy_telnet_command)); write_setting_i(sesskey, "ProxyLogToTerm", conf_get_int(conf, CONF_proxy_log_to_term)); - wmap(sesskey, "Environment", conf, CONF_environmt, TRUE); + wmap(sesskey, "Environment", conf, CONF_environmt, true); write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username)); - write_setting_i(sesskey, "UserNameFromEnvironment", conf_get_int(conf, CONF_username_from_env)); + write_setting_b(sesskey, "UserNameFromEnvironment", conf_get_bool(conf, CONF_username_from_env)); write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername)); - write_setting_i(sesskey, "NoPTY", conf_get_int(conf, CONF_nopty)); - write_setting_i(sesskey, "Compression", conf_get_int(conf, CONF_compression)); - write_setting_i(sesskey, "TryAgent", conf_get_int(conf, CONF_tryagent)); - write_setting_i(sesskey, "AgentFwd", conf_get_int(conf, CONF_agentfwd)); - write_setting_i(sesskey, "GssapiFwd", conf_get_int(conf, CONF_gssapifwd)); - write_setting_i(sesskey, "ChangeUsername", conf_get_int(conf, CONF_change_username)); + write_setting_b(sesskey, "NoPTY", conf_get_bool(conf, CONF_nopty)); + write_setting_b(sesskey, "Compression", conf_get_bool(conf, CONF_compression)); + write_setting_b(sesskey, "TryAgent", conf_get_bool(conf, CONF_tryagent)); + write_setting_b(sesskey, "AgentFwd", conf_get_bool(conf, CONF_agentfwd)); + write_setting_b(sesskey, "GssapiFwd", conf_get_bool(conf, CONF_gssapifwd)); + write_setting_b(sesskey, "ChangeUsername", conf_get_bool(conf, CONF_change_username)); wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); + write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); - write_setting_i(sesskey, "SshNoAuth", conf_get_int(conf, CONF_ssh_no_userauth)); - write_setting_i(sesskey, "SshBanner", conf_get_int(conf, CONF_ssh_show_banner)); - write_setting_i(sesskey, "AuthTIS", conf_get_int(conf, CONF_try_tis_auth)); - write_setting_i(sesskey, "AuthKI", conf_get_int(conf, CONF_try_ki_auth)); - write_setting_i(sesskey, "AuthGSSAPI", conf_get_int(conf, CONF_try_gssapi_auth)); + write_setting_b(sesskey, "SshNoAuth", conf_get_bool(conf, CONF_ssh_no_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)); + write_setting_b(sesskey, "AuthGSSAPI", conf_get_bool(conf, CONF_try_gssapi_auth)); + write_setting_b(sesskey, "AuthGSSAPIKEX", conf_get_bool(conf, CONF_try_gssapi_kex)); #ifndef NO_GSSAPI wprefs(sesskey, "GSSLibs", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); write_setting_filename(sesskey, "GSSCustom", conf_get_filename(conf, CONF_ssh_gss_custom)); #endif - write_setting_i(sesskey, "SshNoShell", conf_get_int(conf, CONF_ssh_no_shell)); + write_setting_b(sesskey, "SshNoShell", conf_get_bool(conf, CONF_ssh_no_shell)); write_setting_i(sesskey, "SshProt", conf_get_int(conf, CONF_sshprot)); write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost)); - write_setting_i(sesskey, "SSH2DES", conf_get_int(conf, CONF_ssh2_des_cbc)); + write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc)); write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); - write_setting_i(sesskey, "RFCEnviron", conf_get_int(conf, CONF_rfc_environ)); - write_setting_i(sesskey, "PassiveTelnet", conf_get_int(conf, CONF_passive_telnet)); - write_setting_i(sesskey, "BackspaceIsDelete", conf_get_int(conf, CONF_bksp_is_delete)); - write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend)); + write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ)); + write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet)); + 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, "NoApplicationKeys", conf_get_int(conf, CONF_no_applic_k)); - write_setting_i(sesskey, "NoApplicationCursors", conf_get_int(conf, CONF_no_applic_c)); - write_setting_i(sesskey, "NoMouseReporting", conf_get_int(conf, CONF_no_mouse_rep)); - write_setting_i(sesskey, "NoRemoteResize", conf_get_int(conf, CONF_no_remote_resize)); - write_setting_i(sesskey, "NoAltScreen", conf_get_int(conf, CONF_no_alt_screen)); - write_setting_i(sesskey, "NoRemoteWinTitle", conf_get_int(conf, CONF_no_remote_wintitle)); - write_setting_i(sesskey, "NoRemoteClearScroll", conf_get_int(conf, CONF_no_remote_clearscroll)); + 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)); + write_setting_b(sesskey, "NoRemoteResize", conf_get_bool(conf, CONF_no_remote_resize)); + write_setting_b(sesskey, "NoAltScreen", conf_get_bool(conf, CONF_no_alt_screen)); + write_setting_b(sesskey, "NoRemoteWinTitle", conf_get_bool(conf, CONF_no_remote_wintitle)); + write_setting_b(sesskey, "NoRemoteClearScroll", conf_get_bool(conf, CONF_no_remote_clearscroll)); write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action)); - write_setting_i(sesskey, "NoDBackspace", conf_get_int(conf, CONF_no_dbackspace)); - write_setting_i(sesskey, "NoRemoteCharset", conf_get_int(conf, CONF_no_remote_charset)); - write_setting_i(sesskey, "ApplicationCursorKeys", conf_get_int(conf, CONF_app_cursor)); - write_setting_i(sesskey, "ApplicationKeypad", conf_get_int(conf, CONF_app_keypad)); - write_setting_i(sesskey, "NetHackKeypad", conf_get_int(conf, CONF_nethack_keypad)); - write_setting_i(sesskey, "AltF4", conf_get_int(conf, CONF_alt_f4)); - write_setting_i(sesskey, "AltSpace", conf_get_int(conf, CONF_alt_space)); - write_setting_i(sesskey, "AltOnly", conf_get_int(conf, CONF_alt_only)); - write_setting_i(sesskey, "ComposeKey", conf_get_int(conf, CONF_compose_key)); - write_setting_i(sesskey, "CtrlAltKeys", conf_get_int(conf, CONF_ctrlaltkeys)); + write_setting_b(sesskey, "NoDBackspace", conf_get_bool(conf, CONF_no_dbackspace)); + write_setting_b(sesskey, "NoRemoteCharset", conf_get_bool(conf, CONF_no_remote_charset)); + write_setting_b(sesskey, "ApplicationCursorKeys", conf_get_bool(conf, CONF_app_cursor)); + write_setting_b(sesskey, "ApplicationKeypad", conf_get_bool(conf, CONF_app_keypad)); + write_setting_b(sesskey, "NetHackKeypad", conf_get_bool(conf, CONF_nethack_keypad)); + write_setting_b(sesskey, "AltF4", conf_get_bool(conf, CONF_alt_f4)); + write_setting_b(sesskey, "AltSpace", conf_get_bool(conf, CONF_alt_space)); + write_setting_b(sesskey, "AltOnly", conf_get_bool(conf, CONF_alt_only)); + write_setting_b(sesskey, "ComposeKey", conf_get_bool(conf, CONF_compose_key)); + write_setting_b(sesskey, "CtrlAltKeys", conf_get_bool(conf, CONF_ctrlaltkeys)); #ifdef OSX_META_KEY_CONFIG - write_setting_i(sesskey, "OSXOptionMeta", conf_get_int(conf, CONF_osx_option_meta)); - write_setting_i(sesskey, "OSXCommandMeta", conf_get_int(conf, CONF_osx_command_meta)); + write_setting_b(sesskey, "OSXOptionMeta", conf_get_bool(conf, CONF_osx_option_meta)); + write_setting_b(sesskey, "OSXCommandMeta", conf_get_bool(conf, CONF_osx_command_meta)); #endif - write_setting_i(sesskey, "TelnetKey", conf_get_int(conf, CONF_telnet_keyboard)); - write_setting_i(sesskey, "TelnetRet", conf_get_int(conf, CONF_telnet_newline)); + write_setting_b(sesskey, "TelnetKey", conf_get_bool(conf, CONF_telnet_keyboard)); + write_setting_b(sesskey, "TelnetRet", conf_get_bool(conf, CONF_telnet_newline)); write_setting_i(sesskey, "LocalEcho", conf_get_int(conf, CONF_localecho)); write_setting_i(sesskey, "LocalEdit", conf_get_int(conf, CONF_localedit)); write_setting_s(sesskey, "Answerback", conf_get_str(conf, CONF_answerback)); - write_setting_i(sesskey, "AlwaysOnTop", conf_get_int(conf, CONF_alwaysontop)); - write_setting_i(sesskey, "FullScreenOnAltEnter", conf_get_int(conf, CONF_fullscreenonaltenter)); - write_setting_i(sesskey, "HideMousePtr", conf_get_int(conf, CONF_hide_mouseptr)); - write_setting_i(sesskey, "SunkenEdge", conf_get_int(conf, CONF_sunken_edge)); + write_setting_b(sesskey, "AlwaysOnTop", conf_get_bool(conf, CONF_alwaysontop)); + write_setting_b(sesskey, "FullScreenOnAltEnter", conf_get_bool(conf, CONF_fullscreenonaltenter)); + write_setting_b(sesskey, "HideMousePtr", conf_get_bool(conf, CONF_hide_mouseptr)); + write_setting_b(sesskey, "SunkenEdge", conf_get_bool(conf, CONF_sunken_edge)); write_setting_i(sesskey, "WindowBorder", conf_get_int(conf, CONF_window_border)); write_setting_i(sesskey, "CurType", conf_get_int(conf, CONF_cursor_type)); - write_setting_i(sesskey, "BlinkCur", conf_get_int(conf, CONF_blink_cur)); + write_setting_b(sesskey, "BlinkCur", conf_get_bool(conf, CONF_blink_cur)); write_setting_i(sesskey, "Beep", conf_get_int(conf, CONF_beep)); write_setting_i(sesskey, "BeepInd", conf_get_int(conf, CONF_beep_ind)); write_setting_filename(sesskey, "BellWaveFile", conf_get_filename(conf, CONF_bell_wavefile)); - write_setting_i(sesskey, "BellOverload", conf_get_int(conf, CONF_bellovl)); + write_setting_b(sesskey, "BellOverload", conf_get_bool(conf, CONF_bellovl)); write_setting_i(sesskey, "BellOverloadN", conf_get_int(conf, CONF_bellovl_n)); write_setting_i(sesskey, "BellOverloadT", conf_get_int(conf, CONF_bellovl_t) #ifdef PUTTY_UNIX_H @@ -592,23 +669,24 @@ void save_open_settings(void *sesskey, Conf *conf) #endif ); write_setting_i(sesskey, "ScrollbackLines", conf_get_int(conf, CONF_savelines)); - write_setting_i(sesskey, "DECOriginMode", conf_get_int(conf, CONF_dec_om)); - write_setting_i(sesskey, "AutoWrapMode", conf_get_int(conf, CONF_wrap_mode)); - write_setting_i(sesskey, "LFImpliesCR", conf_get_int(conf, CONF_lfhascr)); - write_setting_i(sesskey, "CRImpliesLF", conf_get_int(conf, CONF_crhaslf)); - write_setting_i(sesskey, "DisableArabicShaping", conf_get_int(conf, CONF_arabicshaping)); - write_setting_i(sesskey, "DisableBidi", conf_get_int(conf, CONF_bidi)); - write_setting_i(sesskey, "WinNameAlways", conf_get_int(conf, CONF_win_name_always)); + write_setting_b(sesskey, "DECOriginMode", conf_get_bool(conf, CONF_dec_om)); + write_setting_b(sesskey, "AutoWrapMode", conf_get_bool(conf, CONF_wrap_mode)); + write_setting_b(sesskey, "LFImpliesCR", conf_get_bool(conf, CONF_lfhascr)); + write_setting_b(sesskey, "CRImpliesLF", conf_get_bool(conf, CONF_crhaslf)); + write_setting_b(sesskey, "DisableArabicShaping", conf_get_bool(conf, CONF_arabicshaping)); + write_setting_b(sesskey, "DisableBidi", conf_get_bool(conf, CONF_bidi)); + write_setting_b(sesskey, "WinNameAlways", conf_get_bool(conf, CONF_win_name_always)); write_setting_s(sesskey, "WinTitle", conf_get_str(conf, CONF_wintitle)); write_setting_i(sesskey, "TermWidth", conf_get_int(conf, CONF_width)); write_setting_i(sesskey, "TermHeight", conf_get_int(conf, CONF_height)); write_setting_fontspec(sesskey, "Font", conf_get_fontspec(conf, CONF_font)); write_setting_i(sesskey, "FontQuality", conf_get_int(conf, CONF_font_quality)); write_setting_i(sesskey, "FontVTMode", conf_get_int(conf, CONF_vtmode)); - write_setting_i(sesskey, "UseSystemColours", conf_get_int(conf, CONF_system_colour)); - write_setting_i(sesskey, "TryPalette", conf_get_int(conf, CONF_try_palette)); - write_setting_i(sesskey, "ANSIColour", conf_get_int(conf, CONF_ansi_colour)); - write_setting_i(sesskey, "Xterm256Colour", conf_get_int(conf, CONF_xterm_256_colour)); + write_setting_b(sesskey, "UseSystemColours", conf_get_bool(conf, CONF_system_colour)); + write_setting_b(sesskey, "TryPalette", conf_get_bool(conf, CONF_try_palette)); + write_setting_b(sesskey, "ANSIColour", conf_get_bool(conf, CONF_ansi_colour)); + write_setting_b(sesskey, "Xterm256Colour", conf_get_bool(conf, CONF_xterm_256_colour)); + write_setting_b(sesskey, "TrueColour", conf_get_bool(conf, CONF_true_colour)); write_setting_i(sesskey, "BoldAsColour", conf_get_int(conf, CONF_bold_style)-1); for (i = 0; i < 22; i++) { @@ -620,11 +698,13 @@ void save_open_settings(void *sesskey, Conf *conf) conf_get_int_int(conf, CONF_colours, i*3+2)); write_setting_s(sesskey, buf, buf2); } - write_setting_i(sesskey, "RawCNP", conf_get_int(conf, CONF_rawcnp)); - write_setting_i(sesskey, "PasteRTF", conf_get_int(conf, CONF_rtf_paste)); + write_setting_b(sesskey, "RawCNP", conf_get_bool(conf, CONF_rawcnp)); + write_setting_b(sesskey, "UTF8linedraw", conf_get_bool(conf, CONF_utf8linedraw)); + write_setting_b(sesskey, "PasteRTF", conf_get_bool(conf, CONF_rtf_paste)); write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm)); - write_setting_i(sesskey, "RectSelect", conf_get_int(conf, CONF_rect_select)); - write_setting_i(sesskey, "MouseOverride", conf_get_int(conf, CONF_mouse_override)); + write_setting_b(sesskey, "RectSelect", conf_get_bool(conf, CONF_rect_select)); + write_setting_b(sesskey, "PasteControls", conf_get_bool(conf, CONF_paste_controls)); + write_setting_b(sesskey, "MouseOverride", conf_get_bool(conf, CONF_mouse_override)); for (i = 0; i < 256; i += 32) { char buf[20], buf2[256]; int j; @@ -637,26 +717,34 @@ void save_open_settings(void *sesskey, Conf *conf) } write_setting_s(sesskey, buf, buf2); } + write_setting_b(sesskey, "MouseAutocopy", + conf_get_bool(conf, CONF_mouseautocopy)); + write_clip_setting(sesskey, "MousePaste", conf, + CONF_mousepaste, CONF_mousepaste_custom); + write_clip_setting(sesskey, "CtrlShiftIns", conf, + CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + write_clip_setting(sesskey, "CtrlShiftCV", conf, + CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); - write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide)); - write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override)); + write_setting_b(sesskey, "CJKAmbigWide", conf_get_bool(conf, CONF_cjk_ambig_wide)); + write_setting_b(sesskey, "UTF8Override", conf_get_bool(conf, CONF_utf8_override)); write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer)); - write_setting_i(sesskey, "CapsLockCyr", conf_get_int(conf, CONF_xlat_capslockcyr)); - write_setting_i(sesskey, "ScrollBar", conf_get_int(conf, CONF_scrollbar)); - write_setting_i(sesskey, "ScrollBarFullScreen", conf_get_int(conf, CONF_scrollbar_in_fullscreen)); - write_setting_i(sesskey, "ScrollOnKey", conf_get_int(conf, CONF_scroll_on_key)); - write_setting_i(sesskey, "ScrollOnDisp", conf_get_int(conf, CONF_scroll_on_disp)); - write_setting_i(sesskey, "EraseToScrollback", conf_get_int(conf, CONF_erase_to_scrollback)); + write_setting_b(sesskey, "CapsLockCyr", conf_get_bool(conf, CONF_xlat_capslockcyr)); + write_setting_b(sesskey, "ScrollBar", conf_get_bool(conf, CONF_scrollbar)); + write_setting_b(sesskey, "ScrollBarFullScreen", conf_get_bool(conf, CONF_scrollbar_in_fullscreen)); + write_setting_b(sesskey, "ScrollOnKey", conf_get_bool(conf, CONF_scroll_on_key)); + write_setting_b(sesskey, "ScrollOnDisp", conf_get_bool(conf, CONF_scroll_on_disp)); + write_setting_b(sesskey, "EraseToScrollback", conf_get_bool(conf, CONF_erase_to_scrollback)); write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action)); - write_setting_i(sesskey, "BCE", conf_get_int(conf, CONF_bce)); - write_setting_i(sesskey, "BlinkText", conf_get_int(conf, CONF_blinktext)); - write_setting_i(sesskey, "X11Forward", conf_get_int(conf, CONF_x11_forward)); + write_setting_b(sesskey, "BCE", conf_get_bool(conf, CONF_bce)); + write_setting_b(sesskey, "BlinkText", conf_get_bool(conf, CONF_blinktext)); + write_setting_b(sesskey, "X11Forward", conf_get_bool(conf, CONF_x11_forward)); write_setting_s(sesskey, "X11Display", conf_get_str(conf, CONF_x11_display)); write_setting_i(sesskey, "X11AuthType", conf_get_int(conf, CONF_x11_auth)); write_setting_filename(sesskey, "X11AuthFile", conf_get_filename(conf, CONF_xauthfile)); - write_setting_i(sesskey, "LocalPortAcceptAll", conf_get_int(conf, CONF_lport_acceptall)); - write_setting_i(sesskey, "RemotePortAcceptAll", conf_get_int(conf, CONF_rport_acceptall)); - wmap(sesskey, "PortForwardings", conf, CONF_portfwd, TRUE); + write_setting_b(sesskey, "LocalPortAcceptAll", conf_get_bool(conf, CONF_lport_acceptall)); + write_setting_b(sesskey, "RemotePortAcceptAll", conf_get_bool(conf, CONF_rport_acceptall)); + wmap(sesskey, "PortForwardings", conf, CONF_portfwd, true); write_setting_i(sesskey, "BugIgnore1", 2-conf_get_int(conf, CONF_sshbug_ignore1)); write_setting_i(sesskey, "BugPlainPW1", 2-conf_get_int(conf, CONF_sshbug_plainpw1)); write_setting_i(sesskey, "BugRSA1", 2-conf_get_int(conf, CONF_sshbug_rsa1)); @@ -670,13 +758,13 @@ void save_open_settings(void *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, "StampUtmp", conf_get_int(conf, CONF_stamp_utmp)); - write_setting_i(sesskey, "LoginShell", conf_get_int(conf, CONF_login_shell)); - write_setting_i(sesskey, "ScrollbarOnLeft", conf_get_int(conf, CONF_scrollbar_on_left)); + write_setting_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)); write_setting_fontspec(sesskey, "BoldFont", conf_get_fontspec(conf, CONF_boldfont)); write_setting_fontspec(sesskey, "WideFont", conf_get_fontspec(conf, CONF_widefont)); write_setting_fontspec(sesskey, "WideBoldFont", conf_get_fontspec(conf, CONF_wideboldfont)); - write_setting_i(sesskey, "ShadowBold", conf_get_int(conf, CONF_shadowbold)); + write_setting_b(sesskey, "ShadowBold", conf_get_bool(conf, CONF_shadowbold)); write_setting_i(sesskey, "ShadowBoldOffset", conf_get_int(conf, CONF_shadowboldoffset)); write_setting_s(sesskey, "SerialLine", conf_get_str(conf, CONF_serline)); write_setting_i(sesskey, "SerialSpeed", conf_get_int(conf, CONF_serspeed)); @@ -685,15 +773,15 @@ void save_open_settings(void *sesskey, Conf *conf) write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity)); write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow)); write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass)); - write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing)); - write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream)); - write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream)); - wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, FALSE); + write_setting_b(sesskey, "ConnectionSharing", conf_get_bool(conf, CONF_ssh_connection_sharing)); + write_setting_b(sesskey, "ConnectionSharingUpstream", conf_get_bool(conf, CONF_ssh_connection_sharing_upstream)); + write_setting_b(sesskey, "ConnectionSharingDownstream", conf_get_bool(conf, CONF_ssh_connection_sharing_downstream)); + wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, false); } void load_settings(const char *section, Conf *conf) { - void *sesskey; + settings_r *sesskey; sesskey = open_settings_r(section); load_open_settings(sesskey, conf); @@ -703,12 +791,12 @@ void load_settings(const char *section, Conf *conf) add_session_to_jumplist(section); } -void load_open_settings(void *sesskey, Conf *conf) +void load_open_settings(settings_r *sesskey, Conf *conf) { int i; char *prot; - conf_set_int(conf, CONF_ssh_subsys, 0); /* FIXME: load this properly */ + conf_set_bool(conf, CONF_ssh_subsys, false); /* FIXME: load this properly */ conf_set_str(conf, CONF_remote_cmd, ""); conf_set_str(conf, CONF_remote_cmd2, ""); conf_set_str(conf, CONF_ssh_nc_host, ""); @@ -717,17 +805,18 @@ void load_open_settings(void *sesskey, Conf *conf) gppfile(sesskey, "LogFileName", conf, CONF_logfilename); gppi(sesskey, "LogType", 0, conf, CONF_logtype); gppi(sesskey, "LogFileClash", LGXF_ASK, conf, CONF_logxfovr); - gppi(sesskey, "LogFlush", 1, conf, CONF_logflush); - gppi(sesskey, "SSHLogOmitPasswords", 1, conf, CONF_logomitpass); - gppi(sesskey, "SSHLogOmitData", 0, conf, CONF_logomitdata); + gppb(sesskey, "LogFlush", true, conf, CONF_logflush); + gppb(sesskey, "LogHeader", true, conf, CONF_logheader); + gppb(sesskey, "SSHLogOmitPasswords", true, conf, CONF_logomitpass); + gppb(sesskey, "SSHLogOmitData", false, conf, CONF_logomitdata); prot = gpps_raw(sesskey, "Protocol", "default"); conf_set_int(conf, CONF_protocol, default_protocol); conf_set_int(conf, CONF_port, default_port); { - const Backend *b = backend_from_name(prot); - if (b) { - conf_set_int(conf, CONF_protocol, b->protocol); + const struct BackendVtable *vt = backend_vt_from_name(prot); + if (vt) { + conf_set_int(conf, CONF_protocol, vt->protocol); gppi(sesskey, "PortNumber", default_port, conf, CONF_port); } } @@ -739,7 +828,7 @@ void load_open_settings(void *sesskey, Conf *conf) /* The CloseOnExit numbers are arranged in a different order from * the standard FORCE_ON / FORCE_OFF / AUTO. */ i = gppi_raw(sesskey, "CloseOnExit", 1); conf_set_int(conf, CONF_close_on_exit, (i+1)%3); - gppi(sesskey, "WarnOnClose", 1, conf, CONF_warn_on_close); + gppb(sesskey, "WarnOnClose", true, conf, CONF_warn_on_close); { /* This is two values for backward compatibility with 0.50/0.51 */ int pingmin, pingsec; @@ -747,8 +836,8 @@ void load_open_settings(void *sesskey, Conf *conf) pingsec = gppi_raw(sesskey, "PingIntervalSecs", 0); conf_set_int(conf, CONF_ping_interval, pingmin * 60 + pingsec); } - gppi(sesskey, "TCPNoDelay", 1, conf, CONF_tcp_nodelay); - gppi(sesskey, "TCPKeepalives", 0, conf, CONF_tcp_keepalives); + gppb(sesskey, "TCPNoDelay", true, conf, CONF_tcp_nodelay); + gppb(sesskey, "TCPKeepalives", false, conf, CONF_tcp_keepalives); gpps(sesskey, "TerminalType", "xterm", conf, CONF_termtype); gpps(sesskey, "TerminalSpeed", "38400,38400", conf, CONF_termspeed); if (gppmap(sesskey, "TerminalModes", conf, CONF_ttymodes)) { @@ -805,7 +894,7 @@ void load_open_settings(void *sesskey, Conf *conf) /* proxy settings */ gpps(sesskey, "ProxyExcludeList", "", conf, CONF_proxy_exclude_list); i = gppi_raw(sesskey, "ProxyDNS", 1); conf_set_int(conf, CONF_proxy_dns, (i+1)%3); - gppi(sesskey, "ProxyLocalhost", 0, conf, CONF_even_proxy_localhost); + gppb(sesskey, "ProxyLocalhost", false, conf, CONF_even_proxy_localhost); gppi(sesskey, "ProxyMethod", -1, conf, CONF_proxy_type); if (conf_get_int(conf, CONF_proxy_type) == -1) { int i; @@ -835,14 +924,15 @@ void load_open_settings(void *sesskey, Conf *conf) gppi(sesskey, "ProxyLogToTerm", FORCE_OFF, conf, CONF_proxy_log_to_term); gppmap(sesskey, "Environment", conf, CONF_environmt); gpps(sesskey, "UserName", "", conf, CONF_username); - gppi(sesskey, "UserNameFromEnvironment", 0, conf, CONF_username_from_env); + gppb(sesskey, "UserNameFromEnvironment", false, + conf, CONF_username_from_env); gpps(sesskey, "LocalUserName", "", conf, CONF_localusername); - gppi(sesskey, "NoPTY", 0, conf, CONF_nopty); - gppi(sesskey, "Compression", 0, conf, CONF_compression); - gppi(sesskey, "TryAgent", 1, conf, CONF_tryagent); - gppi(sesskey, "AgentFwd", 0, conf, CONF_agentfwd); - gppi(sesskey, "ChangeUsername", 0, conf, CONF_change_username); - gppi(sesskey, "GssapiFwd", 0, conf, CONF_gssapifwd); + gppb(sesskey, "NoPTY", false, conf, CONF_nopty); + gppb(sesskey, "Compression", false, conf, CONF_compression); + gppb(sesskey, "TryAgent", true, conf, CONF_tryagent); + gppb(sesskey, "AgentFwd", false, conf, CONF_agentfwd); + gppb(sesskey, "ChangeUsername", false, conf, CONF_change_username); + gppb(sesskey, "GssapiFwd", false, conf, CONF_gssapifwd); gprefs(sesskey, "Cipher", "\0", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); { @@ -852,7 +942,7 @@ void load_open_settings(void *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-group14-sha1,rsa," "WARN,dh-group1-sha1", *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa," "WARN,dh-group1-sha1,dh-gex-sha1"; @@ -882,49 +972,59 @@ void load_open_settings(void *sesskey, Conf *conf) sfree(raw); raw = dupstr(normal_default); } - gprefs_from_str(raw, kexnames, KEX_MAX, conf, CONF_ssh_kexlist); + /* (For the record: after 0.70, the default algorithm list + * very briefly contained the string 'gss-sha1-krb5'; this was + * never used in any committed version of code, but was left + * over from a pre-commit version of GSS key exchange. + * Mentioned here as it is remotely possible that it will turn + * up in someone's saved settings in future.) */ + + gprefs_from_str(raw, kexnames, KEX_MAX, conf, CONF_ssh_kexlist); sfree(raw); } gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", hknames, HK_MAX, conf, CONF_ssh_hklist); gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); + gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data); { /* SSH-2 only by default */ int sshprot = gppi_raw(sesskey, "SshProt", 3); - /* Old sessions may contain the values correponding to the fallbacks + /* Old sessions may contain the values corresponding to the fallbacks * we used to allow; migrate them */ if (sshprot == 1) sshprot = 0; /* => "SSH-1 only" */ else if (sshprot == 2) sshprot = 3; /* => "SSH-2 only" */ conf_set_int(conf, CONF_sshprot, sshprot); } gpps(sesskey, "LogHost", "", conf, CONF_loghost); - gppi(sesskey, "SSH2DES", 0, conf, CONF_ssh2_des_cbc); - gppi(sesskey, "SshNoAuth", 0, conf, CONF_ssh_no_userauth); - gppi(sesskey, "SshBanner", 1, conf, CONF_ssh_show_banner); - gppi(sesskey, "AuthTIS", 0, conf, CONF_try_tis_auth); - gppi(sesskey, "AuthKI", 1, conf, CONF_try_ki_auth); - gppi(sesskey, "AuthGSSAPI", 1, conf, CONF_try_gssapi_auth); + gppb(sesskey, "SSH2DES", false, conf, CONF_ssh2_des_cbc); + gppb(sesskey, "SshNoAuth", false, conf, CONF_ssh_no_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); + gppb(sesskey, "AuthGSSAPI", true, conf, CONF_try_gssapi_auth); + gppb(sesskey, "AuthGSSAPIKEX", true, conf, CONF_try_gssapi_kex); #ifndef NO_GSSAPI gprefs(sesskey, "GSSLibs", "\0", gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom); #endif - gppi(sesskey, "SshNoShell", 0, conf, CONF_ssh_no_shell); + gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell); gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); - gppi(sesskey, "RFCEnviron", 0, conf, CONF_rfc_environ); - gppi(sesskey, "PassiveTelnet", 0, conf, CONF_passive_telnet); - gppi(sesskey, "BackspaceIsDelete", 1, conf, CONF_bksp_is_delete); - gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend); + gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ); + gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet); + 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, "NoApplicationKeys", 0, conf, CONF_no_applic_k); - gppi(sesskey, "NoApplicationCursors", 0, conf, CONF_no_applic_c); - gppi(sesskey, "NoMouseReporting", 0, conf, CONF_no_mouse_rep); - gppi(sesskey, "NoRemoteResize", 0, conf, CONF_no_remote_resize); - gppi(sesskey, "NoAltScreen", 0, conf, CONF_no_alt_screen); - gppi(sesskey, "NoRemoteWinTitle", 0, conf, CONF_no_remote_wintitle); - gppi(sesskey, "NoRemoteClearScroll", 0, conf, CONF_no_remote_clearscroll); + 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); + gppb(sesskey, "NoRemoteResize", false, conf, CONF_no_remote_resize); + gppb(sesskey, "NoAltScreen", false, conf, CONF_no_alt_screen); + gppb(sesskey, "NoRemoteWinTitle", false, conf, CONF_no_remote_wintitle); + gppb(sesskey, "NoRemoteClearScroll", false, + conf, CONF_no_remote_clearscroll); { /* Backward compatibility */ int no_remote_qtitle = gppi_raw(sesskey, "NoRemoteQTitle", 1); @@ -935,37 +1035,38 @@ void load_open_settings(void *sesskey, Conf *conf) no_remote_qtitle ? TITLE_EMPTY : TITLE_REAL, conf, CONF_remote_qtitle_action); } - gppi(sesskey, "NoDBackspace", 0, conf, CONF_no_dbackspace); - gppi(sesskey, "NoRemoteCharset", 0, conf, CONF_no_remote_charset); - gppi(sesskey, "ApplicationCursorKeys", 0, conf, CONF_app_cursor); - gppi(sesskey, "ApplicationKeypad", 0, conf, CONF_app_keypad); - gppi(sesskey, "NetHackKeypad", 0, conf, CONF_nethack_keypad); - gppi(sesskey, "AltF4", 1, conf, CONF_alt_f4); - gppi(sesskey, "AltSpace", 0, conf, CONF_alt_space); - gppi(sesskey, "AltOnly", 0, conf, CONF_alt_only); - gppi(sesskey, "ComposeKey", 0, conf, CONF_compose_key); - gppi(sesskey, "CtrlAltKeys", 1, conf, CONF_ctrlaltkeys); + gppb(sesskey, "NoDBackspace", false, conf, CONF_no_dbackspace); + gppb(sesskey, "NoRemoteCharset", false, conf, CONF_no_remote_charset); + gppb(sesskey, "ApplicationCursorKeys", false, conf, CONF_app_cursor); + gppb(sesskey, "ApplicationKeypad", false, conf, CONF_app_keypad); + gppb(sesskey, "NetHackKeypad", false, conf, CONF_nethack_keypad); + gppb(sesskey, "AltF4", true, conf, CONF_alt_f4); + gppb(sesskey, "AltSpace", false, conf, CONF_alt_space); + gppb(sesskey, "AltOnly", false, conf, CONF_alt_only); + gppb(sesskey, "ComposeKey", false, conf, CONF_compose_key); + gppb(sesskey, "CtrlAltKeys", true, conf, CONF_ctrlaltkeys); #ifdef OSX_META_KEY_CONFIG - gppi(sesskey, "OSXOptionMeta", 1, conf, CONF_osx_option_meta); - gppi(sesskey, "OSXCommandMeta", 0, conf, CONF_osx_command_meta); + gppb(sesskey, "OSXOptionMeta", true, conf, CONF_osx_option_meta); + gppb(sesskey, "OSXCommandMeta", false, conf, CONF_osx_command_meta); #endif - gppi(sesskey, "TelnetKey", 0, conf, CONF_telnet_keyboard); - gppi(sesskey, "TelnetRet", 1, conf, CONF_telnet_newline); + gppb(sesskey, "TelnetKey", false, conf, CONF_telnet_keyboard); + gppb(sesskey, "TelnetRet", true, conf, CONF_telnet_newline); gppi(sesskey, "LocalEcho", AUTO, conf, CONF_localecho); gppi(sesskey, "LocalEdit", AUTO, conf, CONF_localedit); gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback); - gppi(sesskey, "AlwaysOnTop", 0, conf, CONF_alwaysontop); - gppi(sesskey, "FullScreenOnAltEnter", 0, conf, CONF_fullscreenonaltenter); - gppi(sesskey, "HideMousePtr", 0, conf, CONF_hide_mouseptr); - gppi(sesskey, "SunkenEdge", 0, conf, CONF_sunken_edge); + gppb(sesskey, "AlwaysOnTop", false, conf, CONF_alwaysontop); + gppb(sesskey, "FullScreenOnAltEnter", false, + conf, CONF_fullscreenonaltenter); + gppb(sesskey, "HideMousePtr", false, conf, CONF_hide_mouseptr); + gppb(sesskey, "SunkenEdge", false, conf, CONF_sunken_edge); gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border); gppi(sesskey, "CurType", 0, conf, CONF_cursor_type); - gppi(sesskey, "BlinkCur", 0, conf, CONF_blink_cur); + gppb(sesskey, "BlinkCur", false, conf, CONF_blink_cur); /* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */ gppi(sesskey, "Beep", 1, conf, CONF_beep); gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind); gppfile(sesskey, "BellWaveFile", conf, CONF_bell_wavefile); - gppi(sesskey, "BellOverload", 1, conf, CONF_bellovl); + gppb(sesskey, "BellOverload", true, conf, CONF_bellovl); gppi(sesskey, "BellOverloadN", 5, conf, CONF_bellovl_n); i = gppi_raw(sesskey, "BellOverloadT", 2*TICKSPERSEC #ifdef PUTTY_UNIX_H @@ -988,23 +1089,24 @@ void load_open_settings(void *sesskey, Conf *conf) #endif ); gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines); - gppi(sesskey, "DECOriginMode", 0, conf, CONF_dec_om); - gppi(sesskey, "AutoWrapMode", 1, conf, CONF_wrap_mode); - gppi(sesskey, "LFImpliesCR", 0, conf, CONF_lfhascr); - gppi(sesskey, "CRImpliesLF", 0, conf, CONF_crhaslf); - gppi(sesskey, "DisableArabicShaping", 0, conf, CONF_arabicshaping); - gppi(sesskey, "DisableBidi", 0, conf, CONF_bidi); - gppi(sesskey, "WinNameAlways", 1, conf, CONF_win_name_always); + gppb(sesskey, "DECOriginMode", false, conf, CONF_dec_om); + gppb(sesskey, "AutoWrapMode", true, conf, CONF_wrap_mode); + gppb(sesskey, "LFImpliesCR", false, conf, CONF_lfhascr); + gppb(sesskey, "CRImpliesLF", false, conf, CONF_crhaslf); + gppb(sesskey, "DisableArabicShaping", false, conf, CONF_arabicshaping); + gppb(sesskey, "DisableBidi", false, conf, CONF_bidi); + gppb(sesskey, "WinNameAlways", true, conf, CONF_win_name_always); gpps(sesskey, "WinTitle", "", conf, CONF_wintitle); gppi(sesskey, "TermWidth", 80, conf, CONF_width); gppi(sesskey, "TermHeight", 24, conf, CONF_height); gppfont(sesskey, "Font", conf, CONF_font); gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality); gppi(sesskey, "FontVTMode", VT_UNICODE, conf, CONF_vtmode); - gppi(sesskey, "UseSystemColours", 0, conf, CONF_system_colour); - gppi(sesskey, "TryPalette", 0, conf, CONF_try_palette); - gppi(sesskey, "ANSIColour", 1, conf, CONF_ansi_colour); - gppi(sesskey, "Xterm256Colour", 1, conf, CONF_xterm_256_colour); + gppb(sesskey, "UseSystemColours", false, conf, CONF_system_colour); + gppb(sesskey, "TryPalette", false, conf, CONF_try_palette); + gppb(sesskey, "ANSIColour", true, conf, CONF_ansi_colour); + gppb(sesskey, "Xterm256Colour", true, conf, CONF_xterm_256_colour); + gppb(sesskey, "TrueColour", true, conf, CONF_true_colour); i = gppi_raw(sesskey, "BoldAsColour", 1); conf_set_int(conf, CONF_bold_style, i+1); for (i = 0; i < 22; i++) { @@ -1026,11 +1128,13 @@ void load_open_settings(void *sesskey, Conf *conf) } sfree(buf2); } - gppi(sesskey, "RawCNP", 0, conf, CONF_rawcnp); - gppi(sesskey, "PasteRTF", 0, conf, CONF_rtf_paste); + gppb(sesskey, "RawCNP", false, conf, CONF_rawcnp); + gppb(sesskey, "UTF8linedraw", false, conf, CONF_utf8linedraw); + gppb(sesskey, "PasteRTF", false, conf, CONF_rtf_paste); gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm); - gppi(sesskey, "RectSelect", 0, conf, CONF_rect_select); - gppi(sesskey, "MouseOverride", 1, conf, CONF_mouse_override); + gppb(sesskey, "RectSelect", false, conf, CONF_rect_select); + gppb(sesskey, "PasteControls", false, conf, CONF_paste_controls); + gppb(sesskey, "MouseOverride", true, conf, CONF_mouse_override); for (i = 0; i < 256; i += 32) { static const char *const defaults[] = { "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0", @@ -1057,30 +1161,39 @@ void load_open_settings(void *sesskey, Conf *conf) } sfree(buf2); } + gppb(sesskey, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY, + conf, CONF_mouseautocopy); + read_clip_setting(sesskey, "MousePaste", CLIPUI_DEFAULT_MOUSE, + conf, CONF_mousepaste, CONF_mousepaste_custom); + read_clip_setting(sesskey, "CtrlShiftIns", CLIPUI_DEFAULT_INS, + conf, CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + read_clip_setting(sesskey, "CtrlShiftCV", CLIPUI_NONE, + conf, CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); /* * The empty default for LineCodePage will be converted later * into a plausible default for the locale. */ gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage); - gppi(sesskey, "CJKAmbigWide", 0, conf, CONF_cjk_ambig_wide); - gppi(sesskey, "UTF8Override", 1, conf, CONF_utf8_override); + gppb(sesskey, "CJKAmbigWide", false, conf, CONF_cjk_ambig_wide); + gppb(sesskey, "UTF8Override", true, conf, CONF_utf8_override); gpps(sesskey, "Printer", "", conf, CONF_printer); - gppi(sesskey, "CapsLockCyr", 0, conf, CONF_xlat_capslockcyr); - gppi(sesskey, "ScrollBar", 1, conf, CONF_scrollbar); - gppi(sesskey, "ScrollBarFullScreen", 0, conf, CONF_scrollbar_in_fullscreen); - gppi(sesskey, "ScrollOnKey", 0, conf, CONF_scroll_on_key); - gppi(sesskey, "ScrollOnDisp", 1, conf, CONF_scroll_on_disp); - gppi(sesskey, "EraseToScrollback", 1, conf, CONF_erase_to_scrollback); + gppb(sesskey, "CapsLockCyr", false, conf, CONF_xlat_capslockcyr); + gppb(sesskey, "ScrollBar", true, conf, CONF_scrollbar); + gppb(sesskey, "ScrollBarFullScreen", false, + conf, CONF_scrollbar_in_fullscreen); + gppb(sesskey, "ScrollOnKey", false, conf, CONF_scroll_on_key); + gppb(sesskey, "ScrollOnDisp", true, conf, CONF_scroll_on_disp); + gppb(sesskey, "EraseToScrollback", true, conf, CONF_erase_to_scrollback); gppi(sesskey, "LockSize", 0, conf, CONF_resize_action); - gppi(sesskey, "BCE", 1, conf, CONF_bce); - gppi(sesskey, "BlinkText", 0, conf, CONF_blinktext); - gppi(sesskey, "X11Forward", 0, conf, CONF_x11_forward); + gppb(sesskey, "BCE", true, conf, CONF_bce); + gppb(sesskey, "BlinkText", false, conf, CONF_blinktext); + gppb(sesskey, "X11Forward", false, conf, CONF_x11_forward); gpps(sesskey, "X11Display", "", conf, CONF_x11_display); gppi(sesskey, "X11AuthType", X11_MIT, conf, CONF_x11_auth); gppfile(sesskey, "X11AuthFile", conf, CONF_xauthfile); - gppi(sesskey, "LocalPortAcceptAll", 0, conf, CONF_lport_acceptall); - gppi(sesskey, "RemotePortAcceptAll", 0, conf, CONF_rport_acceptall); + gppb(sesskey, "LocalPortAcceptAll", false, conf, CONF_lport_acceptall); + gppb(sesskey, "RemotePortAcceptAll", false, conf, CONF_rport_acceptall); gppmap(sesskey, "PortForwardings", conf, CONF_portfwd); i = gppi_raw(sesskey, "BugIgnore1", 0); conf_set_int(conf, CONF_sshbug_ignore1, 2-i); i = gppi_raw(sesskey, "BugPlainPW1", 0); conf_set_int(conf, CONF_sshbug_plainpw1, 2-i); @@ -1103,11 +1216,11 @@ void load_open_settings(void *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); - conf_set_int(conf, CONF_ssh_simple, FALSE); - gppi(sesskey, "StampUtmp", 1, conf, CONF_stamp_utmp); - gppi(sesskey, "LoginShell", 1, conf, CONF_login_shell); - gppi(sesskey, "ScrollbarOnLeft", 0, conf, CONF_scrollbar_on_left); - gppi(sesskey, "ShadowBold", 0, conf, CONF_shadowbold); + conf_set_bool(conf, CONF_ssh_simple, false); + gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); + gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); + gppb(sesskey, "ScrollbarOnLeft", false, conf, CONF_scrollbar_on_left); + gppb(sesskey, "ShadowBold", false, conf, CONF_shadowbold); gppfont(sesskey, "BoldFont", conf, CONF_boldfont); gppfont(sesskey, "WideFont", conf, CONF_widefont); gppfont(sesskey, "WideBoldFont", conf, CONF_wideboldfont); @@ -1119,9 +1232,12 @@ void load_open_settings(void *sesskey, Conf *conf) gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity); gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow); gpps(sesskey, "WindowClass", "", conf, CONF_winclass); - gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing); - gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream); - gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream); + gppb(sesskey, "ConnectionSharing", false, + conf, CONF_ssh_connection_sharing); + gppb(sesskey, "ConnectionSharingUpstream", true, + conf, CONF_ssh_connection_sharing_upstream); + gppb(sesskey, "ConnectionSharingDownstream", true, + conf, CONF_ssh_connection_sharing_downstream); gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys); } @@ -1150,34 +1266,22 @@ static int sessioncmp(const void *av, const void *bv) return strcmp(a, b); /* otherwise, compare normally */ } -void get_sesslist(struct sesslist *list, int allocate) +void get_sesslist(struct sesslist *list, bool allocate) { - char otherbuf[2048]; - int buflen, bufsize, i; - char *p, *ret; - void *handle; + int i; + char *p; + settings_e *handle; if (allocate) { + strbuf *sb = strbuf_new(); - buflen = bufsize = 0; - list->buffer = NULL; if ((handle = enum_settings_start()) != NULL) { - do { - ret = enum_settings_next(handle, otherbuf, sizeof(otherbuf)); - if (ret) { - int len = strlen(otherbuf) + 1; - if (bufsize < buflen + len) { - bufsize = buflen + len + 2048; - list->buffer = sresize(list->buffer, bufsize, char); - } - strcpy(list->buffer + buflen, otherbuf); - buflen += strlen(list->buffer + buflen) + 1; - } - } while (ret); + while (enum_settings_next(handle, sb)) + put_byte(sb, '\0'); enum_settings_finish(handle); } - list->buffer = sresize(list->buffer, buflen + 1, char); - list->buffer[buflen] = '\0'; + put_byte(sb, '\0'); + list->buffer = strbuf_to_str(sb); /* * Now set up the list of sessions. Note that "Default diff --git a/sftp.c b/sftp.c index dbbd7401..de02a1ad 100644 --- a/sftp.c +++ b/sftp.c @@ -9,243 +9,45 @@ #include #include "misc.h" -#include "int64.h" #include "tree234.h" #include "sftp.h" -struct sftp_packet { - char *data; - unsigned length, maxlen; - unsigned savedpos; - int type; -}; - static const char *fxp_error_message; static int fxp_errtype; static void fxp_internal_error(const char *msg); /* ---------------------------------------------------------------------- - * SFTP packet construction functions. - */ -static void sftp_pkt_ensure(struct sftp_packet *pkt, int length) -{ - if ((int)pkt->maxlen < length) { - pkt->maxlen = length + 256; - pkt->data = sresize(pkt->data, pkt->maxlen, char); - } -} -static void sftp_pkt_adddata(struct sftp_packet *pkt, - const void *data, int len) -{ - pkt->length += len; - sftp_pkt_ensure(pkt, pkt->length); - memcpy(pkt->data + pkt->length - len, data, len); -} -static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) -{ - sftp_pkt_adddata(pkt, &byte, 1); -} -static void sftp_pkt_adduint32(struct sftp_packet *pkt, - unsigned long value) -{ - unsigned char x[4]; - PUT_32BIT(x, value); - sftp_pkt_adddata(pkt, x, 4); -} -static struct sftp_packet *sftp_pkt_init(int pkt_type) -{ - struct sftp_packet *pkt; - pkt = snew(struct sftp_packet); - pkt->data = NULL; - pkt->savedpos = -1; - pkt->length = 0; - pkt->maxlen = 0; - sftp_pkt_adduint32(pkt, 0); /* length field will be filled in later */ - sftp_pkt_addbyte(pkt, (unsigned char) pkt_type); - return pkt; -} -/* -static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) -{ - sftp_pkt_adddata(pkt, &value, 1); -} -*/ -static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value) -{ - unsigned char x[8]; - PUT_32BIT(x, value.hi); - PUT_32BIT(x + 4, value.lo); - sftp_pkt_adddata(pkt, x, 8); -} -static void sftp_pkt_addstring_start(struct sftp_packet *pkt) -{ - sftp_pkt_adduint32(pkt, 0); - pkt->savedpos = pkt->length; -} -static void sftp_pkt_addstring_str(struct sftp_packet *pkt, const char *data) -{ - sftp_pkt_adddata(pkt, data, strlen(data)); - PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); -} -static void sftp_pkt_addstring_data(struct sftp_packet *pkt, - const char *data, int len) -{ - sftp_pkt_adddata(pkt, data, len); - PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); -} -static void sftp_pkt_addstring(struct sftp_packet *pkt, const char *data) -{ - sftp_pkt_addstring_start(pkt); - sftp_pkt_addstring_str(pkt, data); -} -static void sftp_pkt_addattrs(struct sftp_packet *pkt, struct fxp_attrs attrs) -{ - sftp_pkt_adduint32(pkt, attrs.flags); - if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { - sftp_pkt_adduint32(pkt, attrs.size.hi); - sftp_pkt_adduint32(pkt, attrs.size.lo); - } - if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) { - sftp_pkt_adduint32(pkt, attrs.uid); - sftp_pkt_adduint32(pkt, attrs.gid); - } - if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - sftp_pkt_adduint32(pkt, attrs.permissions); - } - if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { - sftp_pkt_adduint32(pkt, attrs.atime); - sftp_pkt_adduint32(pkt, attrs.mtime); - } - if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) { - /* - * We currently don't support sending any extended - * attributes. - */ - } -} - -/* ---------------------------------------------------------------------- - * SFTP packet decode functions. + * Client-specific parts of the send- and receive-packet system. */ -static int sftp_pkt_getbyte(struct sftp_packet *pkt, unsigned char *ret) -{ - if (pkt->length - pkt->savedpos < 1) - return 0; - *ret = (unsigned char) pkt->data[pkt->savedpos]; - pkt->savedpos++; - return 1; -} -static int sftp_pkt_getuint32(struct sftp_packet *pkt, unsigned long *ret) -{ - if (pkt->length - pkt->savedpos < 4) - return 0; - *ret = GET_32BIT(pkt->data + pkt->savedpos); - pkt->savedpos += 4; - return 1; -} -static int sftp_pkt_getstring(struct sftp_packet *pkt, - char **p, int *length) -{ - *p = NULL; - if (pkt->length - pkt->savedpos < 4) - return 0; - *length = toint(GET_32BIT(pkt->data + pkt->savedpos)); - pkt->savedpos += 4; - if ((int)(pkt->length - pkt->savedpos) < *length || *length < 0) { - *length = 0; - return 0; - } - *p = pkt->data + pkt->savedpos; - pkt->savedpos += *length; - return 1; -} -static int sftp_pkt_getattrs(struct sftp_packet *pkt, struct fxp_attrs *ret) +bool sftp_send(struct sftp_packet *pkt) { - if (!sftp_pkt_getuint32(pkt, &ret->flags)) - return 0; - if (ret->flags & SSH_FILEXFER_ATTR_SIZE) { - unsigned long hi, lo; - if (!sftp_pkt_getuint32(pkt, &hi) || - !sftp_pkt_getuint32(pkt, &lo)) - return 0; - ret->size = uint64_make(hi, lo); - } - if (ret->flags & SSH_FILEXFER_ATTR_UIDGID) { - if (!sftp_pkt_getuint32(pkt, &ret->uid) || - !sftp_pkt_getuint32(pkt, &ret->gid)) - return 0; - } - if (ret->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - if (!sftp_pkt_getuint32(pkt, &ret->permissions)) - return 0; - } - if (ret->flags & SSH_FILEXFER_ATTR_ACMODTIME) { - if (!sftp_pkt_getuint32(pkt, &ret->atime) || - !sftp_pkt_getuint32(pkt, &ret->mtime)) - return 0; - } - if (ret->flags & SSH_FILEXFER_ATTR_EXTENDED) { - unsigned long count; - if (!sftp_pkt_getuint32(pkt, &count)) - return 0; - while (count--) { - char *str; - int len; - /* - * We should try to analyse these, if we ever find one - * we recognise. - */ - if (!sftp_pkt_getstring(pkt, &str, &len) || - !sftp_pkt_getstring(pkt, &str, &len)) - return 0; - } - } - return 1; -} -static void sftp_pkt_free(struct sftp_packet *pkt) -{ - if (pkt->data) - sfree(pkt->data); - sfree(pkt); -} - -/* ---------------------------------------------------------------------- - * Send and receive packet functions. - */ -int sftp_send(struct sftp_packet *pkt) -{ - int ret; - PUT_32BIT(pkt->data, pkt->length - 4); + 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]; - unsigned char uc; if (!sftp_recvdata(x, 4)) return NULL; - pkt = snew(struct sftp_packet); - pkt->savedpos = 0; - pkt->length = pkt->maxlen = GET_32BIT(x); - pkt->data = snewn(pkt->length, char); + pkt = sftp_recv_prepare(GET_32BIT(x)); if (!sftp_recvdata(pkt->data, pkt->length)) { sftp_pkt_free(pkt); return NULL; } - if (!sftp_pkt_getbyte(pkt, &uc)) { + if (!sftp_recv_finish(pkt)) { sftp_pkt_free(pkt); return NULL; - } else { - pkt->type = uc; } return pkt; @@ -259,7 +61,7 @@ struct sftp_packet *sftp_recv(void) struct sftp_request { unsigned id; - int registered; + bool registered; void *userdata; }; @@ -330,7 +132,7 @@ static struct sftp_request *sftp_alloc_request(void) */ r = snew(struct sftp_request); r->id = low + 1 + REQUEST_ID_OFFSET; - r->registered = 0; + r->registered = false; r->userdata = NULL; add234(sftp_requests, r); return r; @@ -346,13 +148,12 @@ void sftp_cleanup_request(void) void sftp_register(struct sftp_request *req) { - req->registered = 1; + req->registered = true; } struct sftp_request *sftp_find_request(struct sftp_packet *pktin) { - unsigned long id; - unsigned fid; + unsigned id; struct sftp_request *req; if (!pktin) { @@ -360,13 +161,13 @@ struct sftp_request *sftp_find_request(struct sftp_packet *pktin) return NULL; } - if (!sftp_pkt_getuint32(pktin, &id)) { + id = get_uint32(pktin); + if (get_err(pktin)) { fxp_internal_error("did not receive a valid SFTP packet\n"); return NULL; } - fid = (unsigned)id; - req = find234(sftp_requests, &fid, sftp_reqfind); + req = find234(sftp_requests, &id, sftp_reqfind); if (!req || !req->registered) { fxp_internal_error("request ID mismatch\n"); return NULL; @@ -377,18 +178,6 @@ struct sftp_request *sftp_find_request(struct sftp_packet *pktin) return req; } -/* ---------------------------------------------------------------------- - * String handling routines. - */ - -static char *mkstr(char *s, int len) -{ - char *p = snewn(len + 1, char); - memcpy(p, s, len); - p[len] = '\0'; - return p; -} - /* ---------------------------------------------------------------------- * SFTP primitives. */ @@ -419,12 +208,11 @@ static int fxp_got_status(struct sftp_packet *pktin) fxp_error_message = "expected FXP_STATUS packet"; fxp_errtype = -1; } else { - unsigned long ul; - if (!sftp_pkt_getuint32(pktin, &ul)) { + fxp_errtype = get_uint32(pktin); + if (get_err(pktin)) { fxp_error_message = "malformed FXP_STATUS packet"; fxp_errtype = -1; } else { - fxp_errtype = ul; if (fxp_errtype < 0 || fxp_errtype >= sizeof(messages) / sizeof(*messages)) fxp_error_message = "unknown error code"; @@ -460,35 +248,36 @@ int fxp_error_type(void) /* * Perform exchange of init/version packets. Return 0 on failure. */ -int fxp_init(void) +bool fxp_init(void) { struct sftp_packet *pktout, *pktin; unsigned long remotever; pktout = sftp_pkt_init(SSH_FXP_INIT); - sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION); + put_uint32(pktout, SFTP_PROTO_VERSION); sftp_send(pktout); pktin = sftp_recv(); if (!pktin) { fxp_internal_error("could not connect"); - return 0; + return false; } if (pktin->type != SSH_FXP_VERSION) { fxp_internal_error("did not receive FXP_VERSION"); sftp_pkt_free(pktin); - return 0; + return false; } - if (!sftp_pkt_getuint32(pktin, &remotever)) { + remotever = get_uint32(pktin); + if (get_err(pktin)) { fxp_internal_error("malformed FXP_VERSION packet"); sftp_pkt_free(pktin); - return 0; + return false; } if (remotever > SFTP_PROTO_VERSION) { fxp_internal_error ("remote protocol is more advanced than we support"); sftp_pkt_free(pktin); - return 0; + return false; } /* * In principle, this packet might also contain extension- @@ -498,7 +287,7 @@ int fxp_init(void) */ sftp_pkt_free(pktin); - return 1; + return true; } /* @@ -510,9 +299,8 @@ struct sftp_request *fxp_realpath_send(const char *path) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_REALPATH); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_str(pktout, path); + put_uint32(pktout, req->id); + put_stringz(pktout, path); sftp_send(pktout); return req; @@ -524,22 +312,24 @@ char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req) if (pktin->type == SSH_FXP_NAME) { unsigned long count; - char *path; - int len; + char *path; + ptrlen name; - if (!sftp_pkt_getuint32(pktin, &count) || count != 1) { + 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; } - if (!sftp_pkt_getstring(pktin, &path, &len)) { + 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(path, len); + path = mkstr(name); sftp_pkt_free(pktin); - return path; + return path; } else { fxp_got_status(pktin); sftp_pkt_free(pktin); @@ -551,44 +341,46 @@ char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req) * Open a file. */ struct sftp_request *fxp_open_send(const char *path, int type, - struct fxp_attrs *attrs) + const struct fxp_attrs *attrs) { struct sftp_request *req = sftp_alloc_request(); struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_OPEN); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, path); - sftp_pkt_adduint32(pktout, type); - if (attrs) - sftp_pkt_addattrs(pktout, *attrs); - else - sftp_pkt_adduint32(pktout, 0); /* empty ATTRS structure */ + 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) { - char *hstring; - struct fxp_handle *handle; - int len; - - if (!sftp_pkt_getstring(pktin, &hstring, &len)) { - fxp_internal_error("OPEN returned malformed FXP_HANDLE\n"); - sftp_pkt_free(pktin); - return NULL; - } - handle = snew(struct fxp_handle); - handle->hstring = mkstr(hstring, len); - handle->hlen = len; - sftp_pkt_free(pktin); - return handle; + return fxp_got_handle(pktin); } else { fxp_got_status(pktin); sftp_pkt_free(pktin); @@ -605,8 +397,8 @@ struct sftp_request *fxp_opendir_send(const char *path) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_OPENDIR); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, path); + put_uint32(pktout, req->id); + put_stringz(pktout, path); sftp_send(pktout); return req; @@ -617,20 +409,7 @@ struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, { sfree(req); if (pktin->type == SSH_FXP_HANDLE) { - char *hstring; - struct fxp_handle *handle; - int len; - - if (!sftp_pkt_getstring(pktin, &hstring, &len)) { - fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n"); - sftp_pkt_free(pktin); - return NULL; - } - handle = snew(struct fxp_handle); - handle->hstring = mkstr(hstring, len); - handle->hlen = len; - sftp_pkt_free(pktin); - return handle; + return fxp_got_handle(pktin); } else { fxp_got_status(pktin); sftp_pkt_free(pktin); @@ -647,9 +426,8 @@ struct sftp_request *fxp_close_send(struct fxp_handle *handle) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_CLOSE); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); sftp_send(pktout); sfree(handle->hstring); @@ -658,7 +436,7 @@ struct sftp_request *fxp_close_send(struct fxp_handle *handle) return req; } -int fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req) +bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req) { sfree(req); fxp_got_status(pktin); @@ -666,30 +444,28 @@ int fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req) return fxp_errtype == SSH_FX_OK; } -struct sftp_request *fxp_mkdir_send(const char *path) +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); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, path); - sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */ + put_uint32(pktout, req->id); + put_stringz(pktout, path); + put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); sftp_send(pktout); return req; } -int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *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); - if (id != 1) { - return 0; - } - return 1; + return id == 1; } struct sftp_request *fxp_rmdir_send(const char *path) @@ -698,23 +474,20 @@ struct sftp_request *fxp_rmdir_send(const char *path) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_RMDIR); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, path); + put_uint32(pktout, req->id); + put_stringz(pktout, path); sftp_send(pktout); return req; } -int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *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); - if (id != 1) { - return 0; - } - return 1; + return id == 1; } struct sftp_request *fxp_remove_send(const char *fname) @@ -723,23 +496,20 @@ struct sftp_request *fxp_remove_send(const char *fname) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_REMOVE); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, fname); + put_uint32(pktout, req->id); + put_stringz(pktout, fname); sftp_send(pktout); return req; } -int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *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); - if (id != 1) { - return 0; - } - return 1; + return id == 1; } struct sftp_request *fxp_rename_send(const char *srcfname, @@ -749,24 +519,21 @@ struct sftp_request *fxp_rename_send(const char *srcfname, struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_RENAME); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, srcfname); - sftp_pkt_addstring(pktout, dstfname); + put_uint32(pktout, req->id); + put_stringz(pktout, srcfname); + put_stringz(pktout, dstfname); sftp_send(pktout); return req; } -int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *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); - if (id != 1) { - return 0; - } - return 1; + return id == 1; } /* @@ -779,29 +546,35 @@ struct sftp_request *fxp_stat_send(const char *fname) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_STAT); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, fname); + put_uint32(pktout, req->id); + put_stringz(pktout, fname); sftp_send(pktout); return req; } -int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *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) { - if (!sftp_pkt_getattrs(pktin, attrs)) { - fxp_internal_error("malformed SSH_FXP_ATTRS packet"); - sftp_pkt_free(pktin); - return 0; - } - sftp_pkt_free(pktin); - return 1; + return fxp_got_attrs(pktin, attrs); } else { fxp_got_status(pktin); sftp_pkt_free(pktin); - return 0; + return false; } } @@ -811,30 +584,25 @@ struct sftp_request *fxp_fstat_send(struct fxp_handle *handle) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_FSTAT); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); sftp_send(pktout); return req; } -int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) +bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, + struct fxp_attrs *attrs) { sfree(req); if (pktin->type == SSH_FXP_ATTRS) { - if (!sftp_pkt_getattrs(pktin, attrs)) { - fxp_internal_error("malformed SSH_FXP_ATTRS packet"); - sftp_pkt_free(pktin); - return 0; - } + return fxp_got_attrs(pktin, attrs); sftp_pkt_free(pktin); - return 1; + return true; } else { fxp_got_status(pktin); sftp_pkt_free(pktin); - return 0; + return false; } } @@ -848,24 +616,21 @@ struct sftp_request *fxp_setstat_send(const char *fname, struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_SETSTAT); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring(pktout, fname); - sftp_pkt_addattrs(pktout, attrs); + put_uint32(pktout, req->id); + put_stringz(pktout, fname); + put_fxp_attrs(pktout, attrs); sftp_send(pktout); return req; } -int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *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); - if (id != 1) { - return 0; - } - return 1; + return id == 1; } struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle, @@ -875,25 +640,21 @@ struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle, struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_FSETSTAT); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); - sftp_pkt_addattrs(pktout, attrs); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); + put_fxp_attrs(pktout, attrs); sftp_send(pktout); return req; } -int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *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); - if (id != 1) { - return 0; - } - return 1; + return id == 1; } /* @@ -903,17 +664,16 @@ int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req) * error indicator. It might even depend on the SFTP server.) */ struct sftp_request *fxp_read_send(struct fxp_handle *handle, - uint64 offset, int len) + uint64_t offset, int len) { struct sftp_request *req = sftp_alloc_request(); struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_READ); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); - sftp_pkt_adduint64(pktout, offset); - sftp_pkt_adduint32(pktout, len); + 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; @@ -924,24 +684,24 @@ int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, { sfree(req); if (pktin->type == SSH_FXP_DATA) { - char *str; - int rlen; + ptrlen data; - if (!sftp_pkt_getstring(pktin, &str, &rlen)) { + 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 (rlen > len || rlen < 0) { + if (data.len > len) { fxp_internal_error("READ returned more bytes than requested"); sftp_pkt_free(pktin); return -1; } - memcpy(buffer, str, rlen); + memcpy(buffer, data.ptr, data.len); sftp_pkt_free(pktin); - return rlen; + return data.len; } else { fxp_got_status(pktin); sftp_pkt_free(pktin); @@ -958,9 +718,8 @@ struct sftp_request *fxp_readdir_send(struct fxp_handle *handle) struct sftp_packet *pktout; pktout = sftp_pkt_init(SSH_FXP_READDIR); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); + put_uint32(pktout, req->id); + put_string(pktout, handle->hstring, handle->hlen); sftp_send(pktout); return req; @@ -974,6 +733,8 @@ struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, 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 @@ -982,8 +743,7 @@ struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, * longname, 4 for a set of attribute flags indicating that * no other attributes are supplied). */ - if (!sftp_pkt_getuint32(pktin, &i) || - i > (pktin->length-pktin->savedpos)/12) { + if (get_err(pktin) || i > get_avail(pktin) / 12) { fxp_internal_error("malformed FXP_NAME packet"); sftp_pkt_free(pktin); return NULL; @@ -1004,23 +764,21 @@ struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, ret->nnames = i; ret->names = snewn(ret->nnames, struct fxp_name); for (i = 0; i < (unsigned long)ret->nnames; i++) { - char *str1, *str2; - int len1, len2; - if (!sftp_pkt_getstring(pktin, &str1, &len1) || - !sftp_pkt_getstring(pktin, &str2, &len2) || - !sftp_pkt_getattrs(pktin, &ret->names[i].attrs)) { - fxp_internal_error("malformed FXP_NAME packet"); - while (i--) { - sfree(ret->names[i].filename); - sfree(ret->names[i].longname); - } - sfree(ret->names); - sfree(ret); - sfree(pktin); - return NULL; - } - ret->names[i].filename = mkstr(str1, len1); - ret->names[i].longname = mkstr(str2, len2); + 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; @@ -1035,24 +793,22 @@ struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin, * Write to a file. Returns 0 on error, 1 on OK. */ struct sftp_request *fxp_write_send(struct fxp_handle *handle, - char *buffer, uint64 offset, int len) + 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); - sftp_pkt_adduint32(pktout, req->id); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen); - sftp_pkt_adduint64(pktout, offset); - sftp_pkt_addstring_start(pktout); - sftp_pkt_addstring_data(pktout, buffer, len); + 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; } -int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req) +bool fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req) { sfree(req); fxp_got_status(pktin); @@ -1119,18 +875,19 @@ void fxp_set_userdata(struct sftp_request *req, void *data) struct req { char *buffer; int len, retlen, complete; - uint64 offset; + uint64_t offset; struct req *next, *prev; }; struct fxp_xfer { - uint64 offset, furthestdata, filesize; - int req_totalsize, req_maxsize, eof, err; + 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 offset) +static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64_t offset) { struct fxp_xfer *xfer = snew(struct fxp_xfer); @@ -1139,14 +896,14 @@ static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64 offset) xfer->head = xfer->tail = NULL; xfer->req_totalsize = 0; xfer->req_maxsize = 1048576; - xfer->err = 0; - xfer->filesize = uint64_make(ULONG_MAX, ULONG_MAX); - xfer->furthestdata = uint64_make(0, 0); + xfer->err = false; + xfer->filesize = UINT64_MAX; + xfer->furthestdata = 0; return xfer; } -int xfer_done(struct fxp_xfer *xfer) +bool xfer_done(struct fxp_xfer *xfer) { /* * We're finished if we've seen EOF _and_ there are no @@ -1183,20 +940,20 @@ void xfer_download_queue(struct fxp_xfer *xfer) sftp_register(req = fxp_read_send(xfer->fh, rr->offset, rr->len)); fxp_set_userdata(req, rr); - xfer->offset = uint64_add32(xfer->offset, rr->len); + xfer->offset += rr->len; xfer->req_totalsize += rr->len; #ifdef DEBUG_DOWNLOAD - { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing read request %p at %s\n", rr, buf); } + printf("queueing read request %p at %"PRIu64"\n", rr, rr->offset); #endif } } -struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset) +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->eof = false; xfer_download_queue(xfer); return xfer; @@ -1225,7 +982,7 @@ int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) #endif if ((rr->retlen < 0 && fxp_error_type()==SSH_FX_EOF) || rr->retlen == 0) { - xfer->eof = TRUE; + xfer->eof = true; rr->retlen = 0; rr->complete = -1; #ifdef DEBUG_DOWNLOAD @@ -1252,24 +1009,19 @@ int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) * I simply shouldn't have been queueing multiple requests in * the first place... */ - if (rr->retlen > 0 && uint64_compare(xfer->furthestdata, rr->offset) < 0) { + if (rr->retlen > 0 && xfer->furthestdata < rr->offset) { xfer->furthestdata = rr->offset; #ifdef DEBUG_DOWNLOAD - { char buf[40]; - uint64_decimal(xfer->furthestdata, buf); - printf("setting furthestdata = %s\n", buf); } + printf("setting furthestdata = %"PRIu64"\n", xfer->furthestdata); #endif } if (rr->retlen < rr->len) { - uint64 filesize = uint64_add32(rr->offset, - (rr->retlen < 0 ? 0 : rr->retlen)); + uint64_t filesize = rr->offset + (rr->retlen < 0 ? 0 : rr->retlen); #ifdef DEBUG_DOWNLOAD - { char buf[40]; - uint64_decimal(filesize, buf); - printf("short block! trying filesize = %s\n", buf); } + printf("short block! trying filesize = %"PRIu64"\n", filesize); #endif - if (uint64_compare(xfer->filesize, filesize) > 0) { + if (xfer->filesize > filesize) { xfer->filesize = filesize; #ifdef DEBUG_DOWNLOAD printf("actually changing filesize\n"); @@ -1277,7 +1029,7 @@ int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) } } - if (uint64_compare(xfer->furthestdata, xfer->filesize) > 0) { + if (xfer->furthestdata > xfer->filesize) { fxp_error_message = "received a short buffer from FXP_READ, but not" " at EOF"; fxp_errtype = -1; @@ -1290,10 +1042,10 @@ int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) void xfer_set_error(struct fxp_xfer *xfer) { - xfer->err = 1; + xfer->err = true; } -int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len) +bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len) { void *retbuf = NULL; int retlen = 0; @@ -1329,12 +1081,12 @@ int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len) if (retbuf) { *buf = retbuf; *len = retlen; - return 1; + return true; } else - return 0; + return false; } -struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset) +struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset) { struct fxp_xfer *xfer = xfer_init(fh, offset); @@ -1346,17 +1098,14 @@ struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset) * from us is whether the outstanding requests have been * handled once that's done. */ - xfer->eof = 1; + xfer->eof = true; return xfer; } -int xfer_upload_ready(struct fxp_xfer *xfer) +bool xfer_upload_ready(struct fxp_xfer *xfer) { - if (sftp_sendbuffer() == 0) - return 1; - else - return 0; + return sftp_sendbuffer() == 0; } void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len) @@ -1382,11 +1131,12 @@ void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len) sftp_register(req = fxp_write_send(xfer->fh, buffer, rr->offset, len)); fxp_set_userdata(req, rr); - xfer->offset = uint64_add32(xfer->offset, rr->len); + xfer->offset += rr->len; xfer->req_totalsize += rr->len; #ifdef DEBUG_UPLOAD - { char buf[40]; uint64_decimal(rr->offset, buf); printf("queueing write request %p at %s [len %d]\n", rr, buf, len); } + printf("queueing write request %p at %"PRIu64" [len %d]\n", + rr, rr->offset, len); #endif } @@ -1398,7 +1148,7 @@ int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) { struct sftp_request *rreq; struct req *rr, *prev, *next; - int ret; + bool ret; rreq = sftp_find_request(pktin); if (!rreq) @@ -1410,7 +1160,7 @@ int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin) } ret = fxp_write_recv(pktin, rreq); #ifdef DEBUG_UPLOAD - printf("write request %p has returned [%d]\n", rr, ret); + printf("write request %p has returned [%d]\n", rr, ret ? 1 : 0); #endif /* diff --git a/sftp.h b/sftp.h index d9fa6939..95d70ff7 100644 --- a/sftp.h +++ b/sftp.h @@ -2,7 +2,7 @@ * sftp.h: definitions for SFTP and the sftp.c routines. */ -#include "int64.h" +#include "defs.h" #define SSH_FXP_INIT 1 /* 0x1 */ #define SSH_FXP_VERSION 2 /* 0x2 */ @@ -55,21 +55,24 @@ #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, or it returns failure. + * until len is available and then returns true, or it returns false + * for failure. * - * Both functions return 1 on success, 0 on failure. + * sftp_senddata returns true on success, false on failure. * * sftp_sendbuffer returns the size of the backlog of data in the * transmit queue. */ -int sftp_senddata(char *data, int len); +bool sftp_senddata(char *data, int len); int sftp_sendbuffer(void); -int sftp_recvdata(char *data, int len); +bool sftp_recvdata(char *data, int len); /* * Free sftp_requests @@ -78,13 +81,14 @@ void sftp_cleanup_request(void); struct fxp_attrs { unsigned long flags; - uint64 size; + 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 @@ -95,9 +99,9 @@ struct fxp_attrs { ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \ (attrs).permissions = (perms)) : \ ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS)) -#define GET_PERMISSIONS(attrs) \ +#define GET_PERMISSIONS(attrs, defaultperms) \ ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \ - (attrs).permissions : -1) + (attrs).permissions : defaultperms) struct fxp_handle { char *hstring; @@ -115,15 +119,55 @@ struct fxp_names { }; struct sftp_request; -struct sftp_packet; + +/* + * Packet-manipulation functions. + */ + +struct sftp_packet { + char *data; + unsigned length, maxlen; + unsigned 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 0 on failure. + * Perform exchange of init/version packets. Return false on failure. */ -int fxp_init(void); +bool fxp_init(void); /* * Canonify a pathname. Concatenate the two given path elements @@ -137,7 +181,7 @@ char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req); * if it's being created. */ struct sftp_request *fxp_open_send(const char *path, int type, - struct fxp_attrs *attrs); + const struct fxp_attrs *attrs); struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, struct sftp_request *req); @@ -149,70 +193,71 @@ struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, struct sftp_request *req); /* - * Close a file/dir. Returns 1 on success, 0 on error. + * Close a file/dir. Returns true on success, false on error. */ struct sftp_request *fxp_close_send(struct fxp_handle *handle); -int fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); +bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); /* * Make a directory. */ -struct sftp_request *fxp_mkdir_send(const char *path); -int fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req); +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); -int fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); +bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); /* * Remove a file. */ struct sftp_request *fxp_remove_send(const char *fname); -int fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req); +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); -int fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); +bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); /* * Return file attributes. */ struct sftp_request *fxp_stat_send(const char *fname); -int fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs); +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); -int fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs); +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); -int fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req); +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); -int fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req); +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 offset, int len); + uint64_t offset, int len); int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, - char *buffer, int len); + char *buffer, int len); /* - * Write to a file. Returns 0 on error, 1 on OK. + * Write to a file. */ struct sftp_request *fxp_write_send(struct fxp_handle *handle, - char *buffer, uint64 offset, int len); -int fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req); + void *buffer, uint64_t offset, int len); +bool fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req); /* * Read from a directory. @@ -254,16 +299,221 @@ struct sftp_packet *sftp_recv(void); struct fxp_xfer; -struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64 offset); +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); -int xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); +bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); -struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64 offset); -int xfer_upload_ready(struct fxp_xfer *xfer); +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); -int xfer_done(struct fxp_xfer *xfer); +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); +}; + +#define sftpsrv_new(vt) \ + ((vt)->new(vt)) +#define sftpsrv_free(srv) \ + ((srv)->vt->free(srv)) +#define sftpsrv_realpath(srv, reply, path) \ + ((srv)->vt->realpath(srv, reply, path)) +#define sftpsrv_open(srv, reply, path, flags, attrs) \ + ((srv)->vt->open(srv, reply, path, flags, attrs)) +#define sftpsrv_opendir(srv, reply, path) \ + ((srv)->vt->opendir(srv, reply, path)) +#define sftpsrv_close(srv, reply, handle) \ + ((srv)->vt->close(srv, reply, handle)) +#define sftpsrv_mkdir(srv, reply, path, attrs) \ + ((srv)->vt->mkdir(srv, reply, path, attrs)) +#define sftpsrv_rmdir(srv, reply, path) \ + ((srv)->vt->rmdir(srv, reply, path)) +#define sftpsrv_remove(srv, reply, path) \ + ((srv)->vt->remove(srv, reply, path)) +#define sftpsrv_rename(srv, reply, srcpath, dstpath) \ + ((srv)->vt->rename(srv, reply, srcpath, dstpath)) +#define sftpsrv_stat(srv, reply, path, follow) \ + ((srv)->vt->stat(srv, reply, path, follow)) +#define sftpsrv_fstat(srv, reply, handle) \ + ((srv)->vt->fstat(srv, reply, handle)) +#define sftpsrv_setstat(srv, reply, path, attrs) \ + ((srv)->vt->setstat(srv, reply, path, attrs)) +#define sftpsrv_fsetstat(srv, reply, handle, attrs) \ + ((srv)->vt->fsetstat(srv, reply, handle, attrs)) +#define sftpsrv_read(srv, reply, handle, offset, length) \ + ((srv)->vt->read(srv, reply, handle, offset, length)) +#define sftpsrv_write(srv, reply, handle, offset, data) \ + ((srv)->vt->write(srv, reply, handle, offset, data)) +#define sftpsrv_readdir(srv, reply, handle, max, nolongname) \ + ((srv)->vt->readdir(srv, reply, handle, max, nolongname)) + +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); +}; + +#define fxp_reply_ok(reply) \ + ((reply)->vt->reply_ok(reply)) +#define fxp_reply_error(reply, code, msg) \ + ((reply)->vt->reply_error(reply, code, msg)) +#define fxp_reply_simple_name(reply, name) \ + ((reply)->vt->reply_simple_name(reply, name)) +#define fxp_reply_name_count(reply, count) \ + ((reply)->vt->reply_name_count(reply, count)) +#define fxp_reply_full_name(reply, name, longname, attrs) \ + ((reply)->vt->reply_full_name(reply, name, longname, attrs)) +#define fxp_reply_handle(reply, handle) \ + ((reply)->vt->reply_handle(reply, handle)) +#define fxp_reply_data(reply, data) \ + ((reply)->vt->reply_data(reply, data)) +#define fxp_reply_attrs(reply, 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); + + int (*send)(ScpServer *s, const void *data, size_t length); + void (*throttle)(ScpServer *s, bool throttled); + void (*eof)(ScpServer *s); +}; + +#define scp_free(s) ((s)->vt->free(s)) +#define scp_send(s, data, len) ((s)->vt->send(s, data, len)) +#define scp_throttle(s, th) ((s)->vt->throttle(s, th)) +#define scp_eof(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 new file mode 100644 index 00000000..57e737fc --- /dev/null +++ b/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); + unsigned newlen; + + assert(length <= 0xFFFFFFFFU - pkt->length); + + newlen = pkt->length + length; + if (pkt->maxlen < newlen) { + pkt->maxlen = newlen * 5 / 4 + 256; + pkt->data = sresize(pkt->data, pkt->maxlen, char); + } + + memcpy(pkt->data + pkt->length, data, length); + pkt->length = newlen; +} + +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--) { + /* + * 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(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 new file mode 100644 index 00000000..1ff3d378 --- /dev/null +++ b/sftpserver.c @@ -0,0 +1,278 @@ +/* + * 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; + 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 struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = { + default_reply_ok, + default_reply_error, + default_reply_simple_name, + default_reply_name_count, + default_reply_full_name, + default_reply_handle, + default_reply_data, + default_reply_attrs, +}; diff --git a/sign.sh b/sign.sh index bdf6245f..bece850a 100755 --- a/sign.sh +++ b/sign.sh @@ -9,12 +9,28 @@ set -e -keyname=EEF20295D15F7E8A +keyname=38BA7229B7588FD1 +preliminary=false -if test "x$1" = "x-r"; then - shift - keyname=9DFE2648B43434E4 -fi +while :; do + case "$1" in + -r) + shift + keyname=6289A25F4AE8DA82 + ;; + -p) + shift + preliminary=true + ;; + -*) + echo "Unknown option '$1'" >&2 + exit 1 + ;; + *) + break + ;; + esac +done sign() { # Check for the prior existence of the signature, so we can @@ -27,9 +43,16 @@ sign() { cd "$1" echo "===== Signing with key '$keyname'" -for i in putty*src.zip putty*.tar.gz w32/*.exe w32/*.zip w32/*.msi w64/*.exe w64/*.zip w64/*.msi w32old/*.exe w32old/*.zip; do - sign --detach-sign "$i" "$i.gpg" -done -for i in md5sums sha1sums sha256sums sha512sums; do - sign --clearsign "$i" "$i.gpg" -done +if $preliminary; then + sign --clearsign sha512sums ../sha512sums-preliminary.gpg +else + for i in putty*src.zip putty*.tar.gz \ + w32/*.exe w32/*.zip w32/*.msi \ + w64/*.exe w64/*.zip w64/*.msi \ + w32old/*.exe w32old/*.zip; do + sign --detach-sign "$i" "$i.gpg" + done + for i in md5sums sha1sums sha256sums sha512sums; do + sign --clearsign "$i" "$i.gpg" + done +fi diff --git a/ssh.c b/ssh.c index 693f52d8..1df7028c 100644 --- a/ssh.c +++ b/ssh.c @@ -13,11153 +13,777 @@ #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 -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif +struct Ssh { + Socket *s; + Seat *seat; + Conf *conf; -/* - * Packet type contexts, so that ssh2_pkt_type can correctly decode - * the ambiguous type numbers back into the correct type strings. - */ -typedef enum { - SSH2_PKTCTX_NOKEX, - SSH2_PKTCTX_DHGROUP, - SSH2_PKTCTX_DHGEX, - SSH2_PKTCTX_ECDHKEX, - SSH2_PKTCTX_RSAKEX -} Pkt_KCtx; -typedef enum { - SSH2_PKTCTX_NOAUTH, - SSH2_PKTCTX_PUBLICKEY, - SSH2_PKTCTX_PASSWORD, - SSH2_PKTCTX_GSSAPI, - SSH2_PKTCTX_KBDINTER -} Pkt_ACtx; - -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", -}; + struct ssh_version_receiver version_receiver; + int remote_bugs; -/* - * Various remote-bug flags. - */ -#define BUG_CHOKES_ON_SSH1_IGNORE 1 -#define BUG_SSH2_HMAC 2 -#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4 -#define BUG_CHOKES_ON_RSA 8 -#define BUG_SSH2_RSA_PADDING 16 -#define BUG_SSH2_DERIVEKEY 32 -#define BUG_SSH2_REKEY 64 -#define BUG_SSH2_PK_SESSIONID 128 -#define BUG_SSH2_MAXPKT 256 -#define BUG_CHOKES_ON_SSH2_IGNORE 512 -#define BUG_CHOKES_ON_WINADJ 1024 -#define BUG_SENDS_LATE_REQUEST_REPLY 2048 -#define BUG_SSH2_OLDGEX 4096 - -#define DH_MIN_SIZE 1024 -#define DH_MAX_SIZE 8192 + Plug plug; + Backend backend; -/* - * Codes for terminal modes. - * Most of these are the same in SSH-1 and SSH-2. - * This list is derived from RFC 4254 and - * SSH-1 RFC-1.2.31. - */ -static const struct ssh_ttymode { - const char* const mode; - int opcode; - enum { TTY_OP_CHAR, TTY_OP_BOOL } type; -} ssh_ttymodes[] = { - /* "V" prefix discarded for special characters relative to SSH specs */ - { "INTR", 1, TTY_OP_CHAR }, - { "QUIT", 2, TTY_OP_CHAR }, - { "ERASE", 3, TTY_OP_CHAR }, - { "KILL", 4, TTY_OP_CHAR }, - { "EOF", 5, TTY_OP_CHAR }, - { "EOL", 6, TTY_OP_CHAR }, - { "EOL2", 7, TTY_OP_CHAR }, - { "START", 8, TTY_OP_CHAR }, - { "STOP", 9, TTY_OP_CHAR }, - { "SUSP", 10, TTY_OP_CHAR }, - { "DSUSP", 11, TTY_OP_CHAR }, - { "REPRINT", 12, TTY_OP_CHAR }, - { "WERASE", 13, TTY_OP_CHAR }, - { "LNEXT", 14, TTY_OP_CHAR }, - { "FLUSH", 15, TTY_OP_CHAR }, - { "SWTCH", 16, TTY_OP_CHAR }, - { "STATUS", 17, TTY_OP_CHAR }, - { "DISCARD", 18, TTY_OP_CHAR }, - { "IGNPAR", 30, TTY_OP_BOOL }, - { "PARMRK", 31, TTY_OP_BOOL }, - { "INPCK", 32, TTY_OP_BOOL }, - { "ISTRIP", 33, TTY_OP_BOOL }, - { "INLCR", 34, TTY_OP_BOOL }, - { "IGNCR", 35, TTY_OP_BOOL }, - { "ICRNL", 36, TTY_OP_BOOL }, - { "IUCLC", 37, TTY_OP_BOOL }, - { "IXON", 38, TTY_OP_BOOL }, - { "IXANY", 39, TTY_OP_BOOL }, - { "IXOFF", 40, TTY_OP_BOOL }, - { "IMAXBEL", 41, TTY_OP_BOOL }, - { "IUTF8", 42, TTY_OP_BOOL }, - { "ISIG", 50, TTY_OP_BOOL }, - { "ICANON", 51, TTY_OP_BOOL }, - { "XCASE", 52, TTY_OP_BOOL }, - { "ECHO", 53, TTY_OP_BOOL }, - { "ECHOE", 54, TTY_OP_BOOL }, - { "ECHOK", 55, TTY_OP_BOOL }, - { "ECHONL", 56, TTY_OP_BOOL }, - { "NOFLSH", 57, TTY_OP_BOOL }, - { "TOSTOP", 58, TTY_OP_BOOL }, - { "IEXTEN", 59, TTY_OP_BOOL }, - { "ECHOCTL", 60, TTY_OP_BOOL }, - { "ECHOKE", 61, TTY_OP_BOOL }, - { "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */ - { "OPOST", 70, TTY_OP_BOOL }, - { "OLCUC", 71, TTY_OP_BOOL }, - { "ONLCR", 72, TTY_OP_BOOL }, - { "OCRNL", 73, TTY_OP_BOOL }, - { "ONOCR", 74, TTY_OP_BOOL }, - { "ONLRET", 75, TTY_OP_BOOL }, - { "CS7", 90, TTY_OP_BOOL }, - { "CS8", 91, TTY_OP_BOOL }, - { "PARENB", 92, TTY_OP_BOOL }, - { "PARODD", 93, TTY_OP_BOOL } -}; + Ldisc *ldisc; + LogContext *logctx; -/* Miscellaneous other tty-related constants. */ -#define SSH_TTY_OP_END 0 -/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */ -#define SSH1_TTY_OP_ISPEED 192 -#define SSH1_TTY_OP_OSPEED 193 -#define SSH2_TTY_OP_ISPEED 128 -#define SSH2_TTY_OP_OSPEED 129 + /* The last list returned from get_specials. */ + SessionSpecial *specials; -/* Helper functions for parsing tty-related config. */ -static unsigned int ssh_tty_parse_specchar(char *s) -{ - unsigned int ret; - if (*s) { - char *next = NULL; - ret = ctrlparse(s, &next); - if (!next) ret = s[0]; - } else { - ret = 255; /* special value meaning "don't set" */ - } - return ret; -} -static unsigned int ssh_tty_parse_boolean(char *s) -{ - if (stricmp(s, "yes") == 0 || - stricmp(s, "on") == 0 || - stricmp(s, "true") == 0 || - stricmp(s, "+") == 0) - return 1; /* true */ - else if (stricmp(s, "no") == 0 || - stricmp(s, "off") == 0 || - stricmp(s, "false") == 0 || - stricmp(s, "-") == 0) - return 0; /* false */ - else - return (atoi(s) != 0); -} + bool bare_connection; + ssh_sharing_state *connshare; + bool attempting_connshare; -#define translate(x) if (type == x) return #x -#define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x -#define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x -static const char *ssh1_pkt_type(int type) -{ - translate(SSH1_MSG_DISCONNECT); - translate(SSH1_SMSG_PUBLIC_KEY); - translate(SSH1_CMSG_SESSION_KEY); - translate(SSH1_CMSG_USER); - translate(SSH1_CMSG_AUTH_RSA); - translate(SSH1_SMSG_AUTH_RSA_CHALLENGE); - translate(SSH1_CMSG_AUTH_RSA_RESPONSE); - translate(SSH1_CMSG_AUTH_PASSWORD); - translate(SSH1_CMSG_REQUEST_PTY); - translate(SSH1_CMSG_WINDOW_SIZE); - translate(SSH1_CMSG_EXEC_SHELL); - translate(SSH1_CMSG_EXEC_CMD); - translate(SSH1_SMSG_SUCCESS); - translate(SSH1_SMSG_FAILURE); - translate(SSH1_CMSG_STDIN_DATA); - translate(SSH1_SMSG_STDOUT_DATA); - translate(SSH1_SMSG_STDERR_DATA); - translate(SSH1_CMSG_EOF); - translate(SSH1_SMSG_EXIT_STATUS); - translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - translate(SSH1_MSG_CHANNEL_OPEN_FAILURE); - translate(SSH1_MSG_CHANNEL_DATA); - translate(SSH1_MSG_CHANNEL_CLOSE); - translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); - translate(SSH1_SMSG_X11_OPEN); - translate(SSH1_CMSG_PORT_FORWARD_REQUEST); - translate(SSH1_MSG_PORT_OPEN); - translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING); - translate(SSH1_SMSG_AGENT_OPEN); - translate(SSH1_MSG_IGNORE); - translate(SSH1_CMSG_EXIT_CONFIRMATION); - translate(SSH1_CMSG_X11_REQUEST_FORWARDING); - translate(SSH1_CMSG_AUTH_RHOSTS_RSA); - translate(SSH1_MSG_DEBUG); - translate(SSH1_CMSG_REQUEST_COMPRESSION); - translate(SSH1_CMSG_AUTH_TIS); - translate(SSH1_SMSG_AUTH_TIS_CHALLENGE); - translate(SSH1_CMSG_AUTH_TIS_RESPONSE); - translate(SSH1_CMSG_AUTH_CCARD); - translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE); - translate(SSH1_CMSG_AUTH_CCARD_RESPONSE); - return "unknown"; -} -static const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, - int type) -{ - translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI); - translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI); - translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI); - translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI); - translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI); - translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI); - translate(SSH2_MSG_DISCONNECT); - translate(SSH2_MSG_IGNORE); - translate(SSH2_MSG_UNIMPLEMENTED); - translate(SSH2_MSG_DEBUG); - translate(SSH2_MSG_SERVICE_REQUEST); - translate(SSH2_MSG_SERVICE_ACCEPT); - translate(SSH2_MSG_KEXINIT); - translate(SSH2_MSG_NEWKEYS); - translatek(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP); - translatek(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP); - translatek(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, SSH2_PKTCTX_DHGEX); - translatek(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX); - translatek(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX); - translatek(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX); - translatek(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX); - translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX); - translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX); - translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX); - translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX); - translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX); - translate(SSH2_MSG_USERAUTH_REQUEST); - translate(SSH2_MSG_USERAUTH_FAILURE); - translate(SSH2_MSG_USERAUTH_SUCCESS); - translate(SSH2_MSG_USERAUTH_BANNER); - translatea(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY); - translatea(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD); - translatea(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER); - translatea(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER); - translate(SSH2_MSG_GLOBAL_REQUEST); - translate(SSH2_MSG_REQUEST_SUCCESS); - translate(SSH2_MSG_REQUEST_FAILURE); - translate(SSH2_MSG_CHANNEL_OPEN); - translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); - translate(SSH2_MSG_CHANNEL_OPEN_FAILURE); - translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST); - translate(SSH2_MSG_CHANNEL_DATA); - translate(SSH2_MSG_CHANNEL_EXTENDED_DATA); - translate(SSH2_MSG_CHANNEL_EOF); - translate(SSH2_MSG_CHANNEL_CLOSE); - translate(SSH2_MSG_CHANNEL_REQUEST); - translate(SSH2_MSG_CHANNEL_SUCCESS); - translate(SSH2_MSG_CHANNEL_FAILURE); - return "unknown"; -} -#undef translate -#undef translatec + struct ssh_connection_shared_gss_state gss_state; -/* Enumeration values for fields in SSH-1 packets */ -enum { - PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM, -}; + char *savedhost; + int savedport; + char *fullhostname; -/* - * Coroutine mechanics for the sillier bits of the code. If these - * macros look impenetrable to you, you might find it helpful to - * read - * - * http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html - * - * which explains the theory behind these macros. - * - * In particular, if you are getting `case expression not constant' - * errors when building with MS Visual Studio, this is because MS's - * Edit and Continue debugging feature causes their compiler to - * violate ANSI C. To disable Edit and Continue debugging: - * - * - right-click ssh.c in the FileView - * - click Settings - * - select the C/C++ tab and the General category - * - under `Debug info:', select anything _other_ than `Program - * Database for Edit and Continue'. - */ -#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; -#define crBeginState crBegin(s->crLine) -#define crStateP(t, v) \ - struct t *s; \ - if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \ - s = (v); -#define crState(t) crStateP(t, ssh->t) -#define crFinish(z) } *crLine = 0; return (z); } -#define crFinishV } *crLine = 0; return; } -#define crFinishFree(z) } sfree(s); return (z); } -#define crFinishFreeV } sfree(s); return; } -#define crReturn(z) \ - do {\ - *crLine =__LINE__; return (z); case __LINE__:;\ - } while (0) -#define crReturnV \ - do {\ - *crLine=__LINE__; return; case __LINE__:;\ - } while (0) -#define crStop(z) do{ *crLine = 0; return (z); }while(0) -#define crStopV do{ *crLine = 0; return; }while(0) -#define crWaitUntil(c) do { crReturn(0); } while (!(c)) -#define crWaitUntilV(c) do { crReturnV; } while (!(c)) - -struct Packet; - -static struct Packet *ssh1_pkt_init(int pkt_type); -static struct Packet *ssh2_pkt_init(int pkt_type); -static void ssh_pkt_ensure(struct Packet *, int length); -static void ssh_pkt_adddata(struct Packet *, const void *data, int len); -static void ssh_pkt_addbyte(struct Packet *, unsigned char value); -static void ssh2_pkt_addbool(struct Packet *, unsigned char value); -static void ssh_pkt_adduint32(struct Packet *, unsigned long value); -static void ssh_pkt_addstring_start(struct Packet *); -static void ssh_pkt_addstring_str(struct Packet *, const char *data); -static void ssh_pkt_addstring_data(struct Packet *, const char *data, int len); -static void ssh_pkt_addstring(struct Packet *, const char *data); -static unsigned char *ssh2_mpint_fmt(Bignum b, int *len); -static void ssh1_pkt_addmp(struct Packet *, Bignum b); -static void ssh2_pkt_addmp(struct Packet *, Bignum b); -static int ssh2_pkt_construct(Ssh, struct Packet *); -static void ssh2_pkt_send(Ssh, struct Packet *); -static void ssh2_pkt_send_noqueue(Ssh, struct Packet *); -static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, - struct Packet *pktin); -static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, - struct Packet *pktin); -static void ssh_channel_init(struct ssh_channel *c); -static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin); -static void ssh_channel_got_eof(struct ssh_channel *c); -static void ssh2_channel_check_close(struct ssh_channel *c); -static void ssh_channel_close_local(struct ssh_channel *c, char const *reason); -static void ssh_channel_destroy(struct ssh_channel *c); -static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize); -static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin); + bool fallback_cmd; + int exitcode; -/* - * Buffer management constants. There are several of these for - * various different purposes: - * - * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up - * on a local data stream before we throttle the whole SSH - * connection (in SSH-1 only). Throttling the whole connection is - * pretty drastic so we set this high in the hope it won't - * happen very often. - * - * - SSH_MAX_BACKLOG is the amount of backlog that must build up - * on the SSH connection itself before we defensively throttle - * _all_ local data streams. This is pretty drastic too (though - * thankfully unlikely in SSH-2 since the window mechanism should - * ensure that the server never has any need to throttle its end - * of the connection), so we set this high as well. - * - * - OUR_V2_WINSIZE is the default window size we present on SSH-2 - * channels. - * - * - OUR_V2_BIGWIN is the window size we advertise for the only - * channel in a simple connection. It must be <= INT_MAX. - * - * - OUR_V2_MAXPKT is the official "maximum packet size" we send - * to the remote side. This actually has nothing to do with the - * size of the _packet_, but is instead a limit on the amount - * of data we're willing to receive in a single SSH2 channel - * data message. - * - * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH - * _packet_ we're prepared to cope with. It must be a multiple - * of the cipher block size, and must be at least 35000. - */ + int version; + int conn_throttle_count; + int overall_bufsize; + bool throttled_all; + bool frozen; -#define SSH1_BUFFER_LIMIT 32768 -#define SSH_MAX_BACKLOG 32768 -#define OUR_V2_WINSIZE 16384 -#define OUR_V2_BIGWIN 0x7fffffff -#define OUR_V2_MAXPKT 0x4000UL -#define OUR_V2_PACKETLIMIT 0x9000UL + /* in case we find these out before we have a ConnectionLayer to tell */ + int term_width, term_height; -struct ssh_signkey_with_user_pref_id { - const struct ssh_signkey *alg; - int id; -}; -const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = { - { &ssh_ecdsa_ed25519, HK_ED25519 }, - { &ssh_ecdsa_nistp256, HK_ECDSA }, - { &ssh_ecdsa_nistp384, HK_ECDSA }, - { &ssh_ecdsa_nistp521, HK_ECDSA }, - { &ssh_dss, HK_DSA }, - { &ssh_rsa, HK_RSA }, -}; + bufchain in_raw, out_raw, user_input; + bool pending_close; + IdempotentCallback ic_out_raw; -const static struct ssh_mac *const macs[] = { - &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 -}; -const static struct ssh_mac *const buggymacs[] = { - &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 -}; + PacketLogSettings pls; + struct DataTransferStats stats; -static void *ssh_comp_none_init(void) -{ - return NULL; -} -static void ssh_comp_none_cleanup(void *handle) -{ -} -static int ssh_comp_none_block(void *handle, unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ - return 0; -} -static int ssh_comp_none_disable(void *handle) -{ - return 0; -} -const static struct ssh_compress ssh_comp_none = { - "none", NULL, - ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, - ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, - ssh_comp_none_disable, NULL -}; -extern const struct ssh_compress ssh_zlib; -const static struct ssh_compress *const compressions[] = { - &ssh_zlib, &ssh_comp_none -}; + BinaryPacketProtocol *bpp; -enum { /* channel types */ - CHAN_MAINSESSION, - CHAN_X11, - CHAN_AGENT, - CHAN_SOCKDATA, - /* - * CHAN_SHARING indicates a channel which is tracked here on - * behalf of a connection-sharing downstream. We do almost nothing - * with these channels ourselves: all messages relating to them - * get thrown straight to sshshare.c and passed on almost - * unmodified to downstream. - */ - CHAN_SHARING, /* - * CHAN_ZOMBIE is used to indicate a channel for which we've - * already destroyed the local data source: for instance, if a - * forwarded port experiences a socket error on the local side, we - * immediately destroy its local socket and turn the SSH channel - * into CHAN_ZOMBIE. + * 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. */ - CHAN_ZOMBIE -}; - -typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin); -typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx); -typedef void (*cchandler_fn_t)(struct ssh_channel *, struct Packet *, void *); - -/* - * Each channel has a queue of outstanding CHANNEL_REQUESTS and their - * handlers. - */ -struct outstanding_channel_request { - cchandler_fn_t handler; - void *ctx; - struct outstanding_channel_request *next; -}; + PacketProtocolLayer *base_layer; -/* - * 2-3-4 tree storing channels. - */ -struct ssh_channel { - Ssh ssh; /* pointer back to main context */ - unsigned remoteid, localid; - int type; - /* True if we opened this channel but server hasn't confirmed. */ - int halfopen; /* - * In SSH-1, this value contains four bits: - * - * 1 We have sent SSH1_MSG_CHANNEL_CLOSE. - * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. - * 4 We have received SSH1_MSG_CHANNEL_CLOSE. - * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION. - * - * A channel is completely finished with when all four bits are set. - * - * In SSH-2, the four bits mean: - * - * 1 We have sent SSH2_MSG_CHANNEL_EOF. - * 2 We have sent SSH2_MSG_CHANNEL_CLOSE. - * 4 We have received SSH2_MSG_CHANNEL_EOF. - * 8 We have received SSH2_MSG_CHANNEL_CLOSE. - * - * A channel is completely finished with when we have both sent - * and received CLOSE. - * - * The symbolic constants below use the SSH-2 terminology, which - * is a bit confusing in SSH-1, but we have to use _something_. + * The ConnectionLayer vtable from our connection layer. */ -#define CLOSES_SENT_EOF 1 -#define CLOSES_SENT_CLOSE 2 -#define CLOSES_RCVD_EOF 4 -#define CLOSES_RCVD_CLOSE 8 - int closes; + ConnectionLayer *cl; /* - * 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. + * A dummy ConnectionLayer that can be used for logging sharing + * downstreams that connect before the real one is ready. */ - int pending_eof; + ConnectionLayer cl_dummy; /* - * True if this channel is causing the underlying connection to be - * throttled. + * 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. */ - int throttling_conn; - union { - struct ssh2_data_channel { - bufchain outbuffer; - unsigned remwindow, remmaxpkt; - /* locwindow is signed so we can cope with excess data. */ - int locwindow, locmaxwin; - /* - * remlocwin is the amount of local window that we think - * the remote end had available to it after it sent the - * last data packet or window adjust ack. - */ - int remlocwin; - /* - * These store the list of channel requests that haven't - * been acked. - */ - struct outstanding_channel_request *chanreq_head, *chanreq_tail; - enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; - } v2; - } v; - union { - struct ssh_agent_channel { - bufchain inbuffer; - agent_pending_query *pending; - } a; - struct ssh_x11_channel { - struct X11Connection *xconn; - int initial; - } x11; - struct ssh_pfd_channel { - struct PortForwarding *pf; - } pfd; - struct ssh_sharing_channel { - void *ctx; - } sharing; - } u; -}; + bool session_started; -/* - * 2-3-4 tree storing remote->local port forwardings. SSH-1 and SSH-2 - * use this structure in different ways, reflecting SSH-2's - * altogether saner approach to port forwarding. - * - * In SSH-1, you arrange a remote forwarding by sending the server - * the remote port number, and the local destination host:port. - * When a connection comes in, the server sends you back that - * host:port pair, and you connect to it. This is a ready-made - * security hole if you're not on the ball: a malicious server - * could send you back _any_ host:port pair, so if you trustingly - * connect to the address it gives you then you've just opened the - * entire inside of your corporate network just by connecting - * through it to a dodgy SSH server. Hence, we must store a list of - * host:port pairs we _are_ trying to forward to, and reject a - * connection request from the server if it's not in the list. - * - * In SSH-2, each side of the connection minds its own business and - * doesn't send unnecessary information to the other. You arrange a - * remote forwarding by sending the server just the remote port - * number. When a connection comes in, the server tells you which - * of its ports was connected to; and _you_ have to remember what - * local host:port pair went with that port number. - * - * Hence, in SSH-1 this structure is indexed by destination - * host:port pair, whereas in SSH-2 it is indexed by source port. - */ -struct ssh_portfwd; /* forward declaration */ - -struct ssh_rportfwd { - unsigned sport, dport; - char *shost, *dhost; - char *sportdesc; - void *share_ctx; - struct ssh_portfwd *pfrec; + Pinger *pinger; + + bool need_random_unref; }; -static void free_rportfwd(struct ssh_rportfwd *pf) + +#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, int bufsize); +static void ssh_bpp_output_raw_data_callback(void *vctx); + +LogContext *ssh_get_logctx(Ssh *ssh) { - if (pf) { - sfree(pf->sportdesc); - sfree(pf->shost); - sfree(pf->dhost); - sfree(pf); - } + return ssh->logctx; } -/* - * Separately to the rportfwd tree (which is for looking up port - * open requests from the server), a tree of _these_ structures is - * used to keep track of all the currently open port forwardings, - * so that we can reconfigure in mid-session if the user requests - * it. - */ -struct ssh_portfwd { - enum { DESTROY, KEEP, CREATE } status; - int type; - unsigned sport, dport; - char *saddr, *daddr; - char *sserv, *dserv; - struct ssh_rportfwd *remote; - int addressfamily; - struct PortListener *local; -}; -#define free_portfwd(pf) ( \ - ((pf) ? (sfree((pf)->saddr), sfree((pf)->daddr), \ - sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) ) - -struct Packet { - long length; /* length of packet: see below */ - long forcepad; /* SSH-2: force padding to at least this length */ - int type; /* only used for incoming packets */ - unsigned long sequence; /* SSH-2 incoming sequence number */ - unsigned char *data; /* allocated storage */ - unsigned char *body; /* offset of payload within `data' */ - long savedpos; /* dual-purpose saved packet position: see below */ - long maxlen; /* amount of storage allocated for `data' */ - long encrypted_len; /* for SSH-2 total-size counting */ - - /* - * A note on the 'length' and 'savedpos' fields above. - * - * Incoming packets are set up so that pkt->length is measured - * relative to pkt->body, which itself points to a few bytes after - * pkt->data (skipping some uninteresting header fields including - * the packet type code). The ssh_pkt_get* functions all expect - * this setup, and they also use pkt->savedpos to indicate how far - * through the packet being decoded they've got - and that, too, - * is an offset from pkt->body rather than pkt->data. - * - * During construction of an outgoing packet, however, pkt->length - * is measured relative to the base pointer pkt->data, and - * pkt->body is not really used for anything until the packet is - * ready for sending. In this mode, pkt->savedpos is reused as a - * temporary variable by the addstring functions, which write out - * a string length field and then keep going back and updating it - * as more data is appended to the subsequent string data field; - * pkt->savedpos stores the offset (again relative to pkt->data) - * of the start of the string data field. - */ +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; + ssh->bpp->out_raw->ic = &ssh->ic_out_raw; + ssh->bpp->pls = &ssh->pls; + ssh->bpp->logctx = ssh->logctx; + ssh->bpp->remote_bugs = ssh->remote_bugs; +} - /* Extra metadata used in SSH packet logging mode, allowing us to - * log in the packet header line that the packet came from a - * connection-sharing downstream and what if anything unusual was - * done to it. The additional_log_text field is expected to be a - * static string - it will not be freed. */ - unsigned downstream_id; - const char *additional_log_text; -}; +static void 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 ssh1_protocol(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin); -static void ssh2_protocol(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin); -static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin); -static void ssh1_protocol_setup(Ssh ssh); -static void ssh2_protocol_setup(Ssh ssh); -static void ssh2_bare_connection_protocol_setup(Ssh ssh); -static void ssh_size(void *handle, int width, int height); -static void ssh_special(void *handle, Telnet_Special); -static int ssh2_try_send(struct ssh_channel *c); -static int ssh_send_channel_data(struct ssh_channel *c, - const char *buf, int len); -static void ssh_throttle_all(Ssh ssh, int enable, int bufsize); -static void ssh2_set_window(struct ssh_channel *c, int newwin); -static int ssh_sendbuffer(void *handle); -static int ssh_do_close(Ssh ssh, int notify_exit); -static unsigned long ssh_pkt_getuint32(struct Packet *pkt); -static int ssh2_pkt_getbool(struct Packet *pkt); -static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length); -static void ssh2_timer(void *ctx, unsigned long now); -static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin); -static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin); - -struct rdpkt1_state_tag { - long len, pad, biglen, to_read; - unsigned long realcrc, gotcrc; - unsigned char *p; - int i; - int chunk; - struct Packet *pktin; -}; +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; -struct rdpkt2_state_tag { - long len, pad, payload, packetlen, maclen; - int i; - int cipherblk; - unsigned long incoming_sequence; - struct Packet *pktin; -}; + ssh->session_started = true; -struct rdpkt2_bare_state_tag { - char length[4]; - long packetlen; - int i; - unsigned long incoming_sequence; - struct Packet *pktin; -}; + /* + * 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); -struct queued_handler; -struct queued_handler { - int msg1, msg2; - chandler_fn_t handler; - void *ctx; - struct queued_handler *next; -}; + old_bpp = ssh->bpp; + ssh->remote_bugs = ssh_verstring_get_bugs(old_bpp); -struct ssh_tag { - const struct plug_function_table *fn; - /* the above field _must_ be first in the structure */ + if (!ssh->bare_connection) { + if (ssh->version == 2) { + PacketProtocolLayer *userauth_layer, *transport_child_layer; - char *v_c, *v_s; - void *exhash; + /* + * 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); - Socket s; + ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, false); + ssh_connect_bpp(ssh); - void *ldisc; - void *logctx; +#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 - unsigned char session_key[32]; - int v1_compressing; - int v1_remote_protoflags; - int v1_local_protoflags; - int agentfwd_enabled; - int X11_fwd_enabled; - int remote_bugs; - const struct ssh_cipher *cipher; - void *v1_cipher_ctx; - void *crcda_ctx; - const struct ssh2_cipher *cscipher, *sccipher; - void *cs_cipher_ctx, *sc_cipher_ctx; - const struct ssh_mac *csmac, *scmac; - int csmac_etm, scmac_etm; - void *cs_mac_ctx, *sc_mac_ctx; - const struct ssh_compress *cscomp, *sccomp; - void *cs_comp_ctx, *sc_comp_ctx; - const struct ssh_kex *kex; - const struct ssh_signkey *hostkey; - char *hostkey_str; /* string representation, for easy checking in rekeys */ - unsigned char v2_session_id[SSH2_KEX_MAX_HASH_LEN]; - int v2_session_id_len; - void *kex_ctx; - - int bare_connection; - int attempting_connshare; - void *connshare; + 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); - char *savedhost; - int savedport; - int send_ok; - int echoing, editing; + 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_tryagent), username, + conf_get_bool(ssh->conf, CONF_change_username), + conf_get_bool(ssh->conf, CONF_try_ki_auth), + 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_connect_ppl(ssh, userauth_layer); + transport_child_layer = userauth_layer; + + sfree(username); + } - int session_started; - void *frontend; + 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), + &ssh->gss_state, + &ssh->stats, transport_child_layer, false); + ssh_connect_ppl(ssh, ssh->base_layer); - int ospeed, ispeed; /* temporaries */ - int term_width, term_height; + if (userauth_layer) + ssh2_userauth_set_transport_layer(userauth_layer, + ssh->base_layer); - tree234 *channels; /* indexed by local id */ - struct ssh_channel *mainchan; /* primary session channel */ - int ncmode; /* is primary channel direct-tcpip? */ - int exitcode; - int close_expected; - int clean_exit; + } else { - tree234 *rportfwds, *portfwds; + ssh->bpp = ssh1_bpp_new(ssh->logctx); + ssh_connect_bpp(ssh); - enum { - SSH_STATE_PREPACKET, - SSH_STATE_BEFORE_SIZE, - SSH_STATE_INTERMED, - SSH_STATE_SESSION, - SSH_STATE_CLOSED - } state; + connection_layer = ssh1_connection_new(ssh, ssh->conf, &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); - int size_needed, eof_needed; - int sent_console_eof; - int got_pty; /* affects EOF behaviour on main channel */ + ssh->base_layer = ssh1_login_new( + ssh->conf, ssh->savedhost, ssh->savedport, connection_layer); + ssh_connect_ppl(ssh, ssh->base_layer); - struct Packet **queue; - int queuelen, queuesize; - int queueing; - unsigned char *deferred_send_data; - int deferred_len, deferred_size; + } - /* - * Gross hack: pscp will try to start SFTP but fall back to - * scp1 if that fails. This variable is the means by which - * scp.c can reach into the SSH code and find out which one it - * got. - */ - int fallback_cmd; + } else { + ssh->bpp = ssh2_bare_bpp_new(ssh->logctx); + ssh_connect_bpp(ssh); - bufchain banner; /* accumulates banners during do_ssh2_authconn */ + connection_layer = ssh2_connection_new( + ssh, NULL, false, ssh->conf, ssh_verstring_get_remote(old_bpp), + &ssh->cl); + ssh_connect_ppl(ssh, connection_layer); + ssh->base_layer = connection_layer; + } - Pkt_KCtx pkt_kctx; - Pkt_ACtx pkt_actx; + /* 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); - struct X11Display *x11disp; - struct X11FakeAuth *x11auth; - tree234 *x11authtree; + seat_update_specials_menu(ssh->seat); + ssh->pinger = pinger_new(ssh->conf, &ssh->backend); - int version; - int conn_throttle_count; - int overall_bufsize; - int throttled_all; - int v1_stdout_throttling; - unsigned long v2_outgoing_sequence; - - int ssh1_rdpkt_crstate; - int ssh2_rdpkt_crstate; - int ssh2_bare_rdpkt_crstate; - int ssh_gotdata_crstate; - int do_ssh1_connection_crstate; - - void *do_ssh_init_state; - void *do_ssh1_login_state; - void *do_ssh2_transport_state; - void *do_ssh2_authconn_state; - void *do_ssh_connection_init_state; - - struct rdpkt1_state_tag rdpkt1_state; - struct rdpkt2_state_tag rdpkt2_state; - struct rdpkt2_bare_state_tag rdpkt2_bare_state; - - /* SSH-1 and SSH-2 use this for different things, but both use it */ - int protocol_initial_phase_done; - - void (*protocol) (Ssh ssh, const void *vin, int inlen, - struct Packet *pkt); - struct Packet *(*s_rdpkt) (Ssh ssh, const unsigned char **data, - int *datalen); - int (*do_ssh_init)(Ssh ssh, unsigned char c); + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + ssh_ppl_process_queue(ssh->base_layer); - /* - * We maintain our own copy of a Conf structure here. That way, - * when we're passed a new one for reconfiguration, we can check - * the differences and potentially reconfigure port forwardings - * etc in mid-session. - */ - Conf *conf; + /* Pass in the initial terminal size, if we knew it already. */ + ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); - /* - * Values cached out of conf so as to avoid the tree234 lookup - * cost every time they're used. - */ - int logomitdata; + ssh_bpp_free(old_bpp); +} - /* - * Dynamically allocated username string created during SSH - * login. Stored in here rather than in the coroutine state so - * that it'll be reliably freed if we shut down the SSH session - * at some unexpected moment. - */ - char *username; +static void ssh_bpp_output_raw_data_callback(void *vctx) +{ + Ssh *ssh = (Ssh *)vctx; - /* - * Used to transfer data back from async callbacks. - */ - void *agent_response; - int agent_response_len; - int user_response; + if (!ssh->s) + return; - /* - * The SSH connection can be set as `frozen', meaning we are - * not currently accepting incoming data from the network. This - * is slightly more serious than setting the _socket_ as - * frozen, because we may already have had data passed to us - * from the network which we need to delay processing until - * after the freeze is lifted, so we also need a bufchain to - * store that data. - */ - int frozen; - bufchain queued_incoming_data; + while (bufchain_size(&ssh->out_raw) > 0) { + void *data; + int len, backlog; - /* - * Dispatch table for packet types that we may have to deal - * with at any time. - */ - handler_fn_t packet_dispatch[256]; + bufchain_prefix(&ssh->out_raw, &data, &len); - /* - * Queues of one-off handler functions for success/failure - * indications from a request. - */ - struct queued_handler *qhead, *qtail; - handler_fn_t q_saved_handler1, q_saved_handler2; + if (ssh->logctx) + log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + backlog = sk_write(ssh->s, data, len); - /* - * This module deals with sending keepalives. - */ - Pinger pinger; + bufchain_consume(&ssh->out_raw, len); - /* - * Track incoming and outgoing data sizes and time, for - * size-based rekeys. - */ - unsigned long incoming_data_size, outgoing_data_size, deferred_data_size; - unsigned long max_data_size; - int kex_in_progress; - unsigned long next_rekey, last_rekey; - const char *deferred_rekey_reason; + if (backlog > SSH_MAX_BACKLOG) { + ssh_throttle_all(ssh, true, backlog); + return; + } + } - /* - * Fully qualified host name, which we need if doing GSSAPI. - */ - char *fullhostname; + if (ssh->pending_close) { + sk_close(ssh->s); + ssh->s = NULL; + } +} -#ifndef NO_GSSAPI - /* - * GSSAPI libraries for this session. - */ - struct ssh_gss_liblist *gsslibs; -#endif +static void ssh_shutdown_internal(Ssh *ssh) +{ + expire_timer_context(ssh); - /* - * The last list returned from get_specials. - */ - struct telnet_special *specials; + if (ssh->connshare) { + sharestate_free(ssh->connshare); + ssh->connshare = NULL; + } - /* - * 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[lenof(hostkey_algs)]; - int n_uncert_hostkeys; + if (ssh->pinger) { + pinger_free(ssh->pinger); + ssh->pinger = NULL; + } /* - * Flag indicating that the current rekey is intended to finish - * with a newly cross-certified host key. + * We only need to free the base PPL, which will free the others + * (if any) transitively. */ - int cross_certifying; + if (ssh->base_layer) { + ssh_ppl_free(ssh->base_layer); + ssh->base_layer = NULL; + } - /* - * Any asynchronous query to our SSH agent that we might have in - * flight from the main authentication loop. (Queries from - * agent-forwarding channels live in their channel structure.) - */ - agent_pending_query *auth_agent_query; -}; + ssh->cl = NULL; +} -static const char *ssh_pkt_type(Ssh ssh, int type) +static void ssh_shutdown(Ssh *ssh) { - if (ssh->version == 1) - return ssh1_pkt_type(type); - else - return ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, type); -} + ssh_shutdown_internal(ssh); -#define logevent(s) logevent(ssh->frontend, s) + if (ssh->bpp) { + ssh_bpp_free(ssh->bpp); + ssh->bpp = NULL; + } -/* logevent, only printf-formatted. */ -static void logeventf(Ssh ssh, const char *fmt, ...) -{ - va_list ap; - char *buf; + if (ssh->s) { + sk_close(ssh->s); + ssh->s = NULL; + } - va_start(ap, fmt); - buf = dupvprintf(fmt, ap); - va_end(ap); - logevent(buf); - sfree(buf); + bufchain_clear(&ssh->in_raw); + bufchain_clear(&ssh->out_raw); + bufchain_clear(&ssh->user_input); } -static void bomb_out(Ssh ssh, char *text) +static void ssh_initiate_connection_close(Ssh *ssh) { - ssh_do_close(ssh, FALSE); - logevent(text); - connection_fatal(ssh->frontend, "%s", text); - sfree(text); + /* 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 bombout(msg) bomb_out(ssh, dupprintf msg) +#define GET_FORMATTED_MSG \ + char *msg; \ + va_list ap; \ + va_start(ap, fmt); \ + msg = dupvprintf(fmt, ap); \ + va_end(ap); -/* Helper function for common bits of parsing ttymodes. */ -static void parse_ttymodes(Ssh ssh, - void (*do_mode)(void *data, - const struct ssh_ttymode *mode, - char *val), - void *data) +void ssh_remote_error(Ssh *ssh, const char *fmt, ...) { - int i; - const struct ssh_ttymode *mode; - char *val; + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; - for (i = 0; i < lenof(ssh_ttymodes); i++) { - mode = ssh_ttymodes + i; - /* Every mode known to the current version of the code should be - * mentioned; this was ensured when settings were loaded. */ - val = conf_get_str_str(ssh->conf, CONF_ttymodes, mode->mode); + /* Error messages sent by the remote don't count as clean exits */ + ssh->exitcode = 128; - /* - * val[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 (val[0] == 'A') { - val = get_ttymode(ssh->frontend, mode->mode); - if (val) { - do_mode(data, mode, val); - sfree(val); - } - } else if (val[0] == 'V') { - do_mode(data, mode, val + 1); /* skip the 'V' */ - } /* else 'N', or something from the future we don't understand */ - } -} + /* Close the socket immediately, since the server has already + * closed its end (or is about to). */ + ssh_shutdown(ssh); -static int ssh_channelcmp(void *av, void *bv) -{ - struct ssh_channel *a = (struct ssh_channel *) av; - struct ssh_channel *b = (struct ssh_channel *) bv; - if (a->localid < b->localid) - return -1; - if (a->localid > b->localid) - return +1; - return 0; -} -static int ssh_channelfind(void *av, void *bv) -{ - unsigned *a = (unsigned *) av; - struct ssh_channel *b = (struct ssh_channel *) bv; - if (*a < b->localid) - return -1; - if (*a > b->localid) - return +1; - return 0; + logevent(ssh->logctx, msg); + seat_connection_fatal(ssh->seat, "%s", msg); + sfree(msg); + } } -static int ssh_rportcmp_ssh1(void *av, void *bv) +void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) { - 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; -} + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; -static int ssh_rportcmp_ssh2(void *av, void *bv) -{ - struct ssh_rportfwd *a = (struct ssh_rportfwd *) av; - struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv; - int i; - if ( (i = strcmp(a->shost, b->shost)) != 0) - return i < 0 ? -1 : +1; - if (a->sport > b->sport) - return +1; - if (a->sport < b->sport) - return -1; - return 0; -} + /* EOF from the remote, if we were expecting it, does count as + * a clean exit */ + ssh->exitcode = 0; -/* - * Special form of strcmp which can cope with NULL inputs. NULL is - * defined to sort before even the empty string. - */ -static int nullstrcmp(const char *a, const char *b) -{ - if (a == NULL && b == NULL) - return 0; - if (a == NULL) - return -1; - if (b == NULL) - return +1; - return strcmp(a, b); -} + /* Close the socket immediately, since the server has already + * closed its end. */ + ssh_shutdown(ssh); -static int ssh_portcmp(void *av, void *bv) -{ - struct ssh_portfwd *a = (struct ssh_portfwd *) av; - struct ssh_portfwd *b = (struct ssh_portfwd *) bv; - int i; - if (a->type > b->type) - return +1; - if (a->type < b->type) - return -1; - if (a->addressfamily > b->addressfamily) - return +1; - if (a->addressfamily < b->addressfamily) - return -1; - if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0) - return i < 0 ? -1 : +1; - if (a->sport > b->sport) - return +1; - if (a->sport < b->sport) - return -1; - if (a->type != 'D') { - if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0) - return i < 0 ? -1 : +1; - if (a->dport > b->dport) - return +1; - if (a->dport < b->dport) - return -1; + 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); } - return 0; } -static int alloc_channel_id(Ssh ssh) +void ssh_proto_error(Ssh *ssh, const char *fmt, ...) { - const unsigned CHANNEL_NUMBER_OFFSET = 256; - unsigned low, high, mid; - int tsize; - struct ssh_channel *c; + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; - /* - * First-fit allocation of channel numbers: always pick the - * lowest unused one. To do this, binary-search using the - * counted B-tree to find the largest channel ID which is in a - * contiguous sequence from the beginning. (Precisely - * everything in that sequence must have ID equal to its tree - * index plus CHANNEL_NUMBER_OFFSET.) - */ - tsize = count234(ssh->channels); - - low = -1; - high = tsize; - while (high - low > 1) { - mid = (high + low) / 2; - c = index234(ssh->channels, mid); - if (c->localid == mid + CHANNEL_NUMBER_OFFSET) - low = mid; /* this one is fine */ - else - high = mid; /* this one is past it */ - } - /* - * Now low points to either -1, or the tree index of the - * largest ID in the initial sequence. - */ - { - unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET; - assert(NULL == find234(ssh->channels, &i, ssh_channelfind)); + 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); } - return low + 1 + CHANNEL_NUMBER_OFFSET; } -static void c_write_stderr(int trusted, const char *buf, int len) +void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) { - int i; - for (i = 0; i < len; i++) - if (buf[i] != '\r' && (trusted || buf[i] == '\n' || (buf[i] & 0x60))) - fputc(buf[i], stderr); -} + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; -static void c_write(Ssh ssh, const char *buf, int len) -{ - if (flags & FLAG_STDERR) - c_write_stderr(1, buf, len); - else - from_backend(ssh->frontend, 1, buf, len); -} + ssh->exitcode = 128; -static void c_write_untrusted(Ssh ssh, const char *buf, int len) -{ - if (flags & FLAG_STDERR) - c_write_stderr(0, buf, len); - else - from_backend_untrusted(ssh->frontend, buf, len); -} + ssh_initiate_connection_close(ssh); -static void c_write_str(Ssh ssh, const char *buf) -{ - c_write(ssh, buf, strlen(buf)); -} + logevent(ssh->logctx, msg); + seat_connection_fatal(ssh->seat, "%s", msg); + sfree(msg); -static void ssh_free_packet(struct Packet *pkt) -{ - sfree(pkt->data); - sfree(pkt); + seat_notify_remote_exit(ssh->seat); + } } -static struct Packet *ssh_new_packet(void) + +void ssh_user_close(Ssh *ssh, const char *fmt, ...) { - struct Packet *pkt = snew(struct Packet); + if (ssh->base_layer || !ssh->session_started) { + GET_FORMATTED_MSG; - pkt->body = pkt->data = NULL; - pkt->maxlen = 0; + /* 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; - return pkt; -} + ssh_initiate_connection_close(ssh); -static void ssh1_log_incoming_packet(Ssh ssh, struct Packet *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - char *str; - int slen; - - pkt->savedpos = 0; - - if (ssh->logomitdata && - (pkt->type == SSH1_SMSG_STDOUT_DATA || - pkt->type == SSH1_SMSG_STDERR_DATA || - pkt->type == SSH1_MSG_CHANNEL_DATA)) { - /* "Session data" packets - omit the data string. */ - if (pkt->type == SSH1_MSG_CHANNEL_DATA) - ssh_pkt_getuint32(pkt); /* skip channel id */ - blanks[nblanks].offset = pkt->savedpos + 4; - blanks[nblanks].type = PKTLOG_OMIT; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = slen; - nblanks++; - } + logevent(ssh->logctx, msg); + sfree(msg); + + seat_notify_remote_exit(ssh->seat); } - log_packet(ssh->logctx, PKT_INCOMING, pkt->type, - ssh1_pkt_type(pkt->type), - pkt->body, pkt->length, nblanks, blanks, NULL, - 0, NULL); } -static void ssh1_log_outgoing_packet(Ssh ssh, struct Packet *pkt) +static void ssh_socket_log(Plug *plug, int type, SockAddr *addr, int port, + const char *error_msg, int error_code) { - int nblanks = 0; - struct logblank_t blanks[4]; - char *str; - int slen; + Ssh *ssh = container_of(plug, Ssh, plug); /* - * For outgoing packets, pkt->length represents the length of the - * whole packet starting at pkt->data (including some header), and - * pkt->body refers to the point within that where the log-worthy - * payload begins. However, incoming packets expect pkt->length to - * represent only the payload length (that is, it's measured from - * pkt->body not from pkt->data). Temporarily adjust our outgoing - * packet to conform to the incoming-packet semantics, so that we - * can analyse it with the ssh_pkt_get functions. + * 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. */ - pkt->length -= (pkt->body - pkt->data); - pkt->savedpos = 0; - - if (ssh->logomitdata && - (pkt->type == SSH1_CMSG_STDIN_DATA || - pkt->type == SSH1_MSG_CHANNEL_DATA)) { - /* "Session data" packets - omit the data string. */ - if (pkt->type == SSH1_MSG_CHANNEL_DATA) - ssh_pkt_getuint32(pkt); /* skip channel id */ - blanks[nblanks].offset = pkt->savedpos + 4; - blanks[nblanks].type = PKTLOG_OMIT; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = slen; - nblanks++; - } - } - if ((pkt->type == SSH1_CMSG_AUTH_PASSWORD || - pkt->type == SSH1_CMSG_AUTH_TIS_RESPONSE || - pkt->type == SSH1_CMSG_AUTH_CCARD_RESPONSE) && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* If this is a password or similar packet, blank the password(s). */ - blanks[nblanks].offset = 0; - blanks[nblanks].len = pkt->length; - blanks[nblanks].type = PKTLOG_BLANK; - nblanks++; - } else if (pkt->type == SSH1_CMSG_X11_REQUEST_FORWARDING && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* - * If this is an X forwarding request packet, blank the fake - * auth data. - * - * Note that while we blank the X authentication data here, we - * don't take any special action to blank the start of an X11 - * channel, so using MIT-MAGIC-COOKIE-1 and actually opening - * an X connection without having session blanking enabled is - * likely to leak your cookie into the log. - */ - pkt->savedpos = 0; - ssh_pkt_getstring(pkt, &str, &slen); - blanks[nblanks].offset = pkt->savedpos; - blanks[nblanks].type = PKTLOG_BLANK; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; - nblanks++; - } - } - - log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12], - ssh1_pkt_type(pkt->data[12]), - pkt->body, pkt->length, - nblanks, blanks, NULL, 0, NULL); - - /* - * Undo the above adjustment of pkt->length, to put the packet - * back in the state we found it. - */ - pkt->length += (pkt->body - pkt->data); + if (!ssh->attempting_connshare) + backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, + error_msg, error_code, ssh->conf, + ssh->session_started); } -/* - * Collect incoming data in the incoming packet buffer. - * Decipher and verify the packet when it is completely read. - * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets. - * Update the *data and *datalen variables. - * Return a Packet structure when a packet is completed. - */ -static struct Packet *ssh1_rdpkt(Ssh ssh, const unsigned char **data, - int *datalen) +static void ssh_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { - struct rdpkt1_state_tag *st = &ssh->rdpkt1_state; + Ssh *ssh = container_of(plug, Ssh, plug); + if (error_msg) { + ssh_remote_error(ssh, "Network error: %s", error_msg); + } else if (ssh->bpp) { + ssh->bpp->input_eof = true; + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } +} - crBegin(ssh->ssh1_rdpkt_crstate); +static void ssh_receive(Plug *plug, int urgent, char *data, int len) +{ + Ssh *ssh = container_of(plug, Ssh, plug); - st->pktin = ssh_new_packet(); - - st->pktin->type = 0; - st->pktin->length = 0; - - for (st->i = st->len = 0; st->i < 4; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->len = (st->len << 8) + **data; - (*data)++, (*datalen)--; - } - - st->pad = 8 - (st->len % 8); - st->biglen = st->len + st->pad; - st->pktin->length = st->len - 5; - - if (st->biglen < 0) { - bombout(("Extremely large packet length from server suggests" - " data stream corruption")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - st->pktin->maxlen = st->biglen; - st->pktin->data = snewn(st->biglen + APIEXTRA, unsigned char); - - st->to_read = st->biglen; - st->p = st->pktin->data; - while (st->to_read > 0) { - st->chunk = st->to_read; - while ((*datalen) == 0) - crReturn(NULL); - if (st->chunk > (*datalen)) - st->chunk = (*datalen); - memcpy(st->p, *data, st->chunk); - *data += st->chunk; - *datalen -= st->chunk; - st->p += st->chunk; - st->to_read -= st->chunk; - } - - if (ssh->cipher && detect_attack(ssh->crcda_ctx, st->pktin->data, - st->biglen, NULL)) { - bombout(("Network attack (CRC compensation) detected!")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - if (ssh->cipher) - ssh->cipher->decrypt(ssh->v1_cipher_ctx, st->pktin->data, st->biglen); - - st->realcrc = crc32_compute(st->pktin->data, st->biglen - 4); - st->gotcrc = GET_32BIT(st->pktin->data + st->biglen - 4); - if (st->gotcrc != st->realcrc) { - bombout(("Incorrect CRC received on packet")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - st->pktin->body = st->pktin->data + st->pad + 1; - - if (ssh->v1_compressing) { - unsigned char *decompblk; - int decomplen; - if (!zlib_decompress_block(ssh->sc_comp_ctx, - st->pktin->body - 1, st->pktin->length + 1, - &decompblk, &decomplen)) { - bombout(("Zlib decompression encountered invalid data")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - if (st->pktin->maxlen < st->pad + decomplen) { - st->pktin->maxlen = st->pad + decomplen; - st->pktin->data = sresize(st->pktin->data, - st->pktin->maxlen + APIEXTRA, - unsigned char); - st->pktin->body = st->pktin->data + st->pad + 1; - } - - memcpy(st->pktin->body - 1, decompblk, decomplen); - sfree(decompblk); - st->pktin->length = decomplen - 1; - } - - st->pktin->type = st->pktin->body[-1]; - - /* - * Now pktin->body and pktin->length identify the semantic content - * of the packet, excluding the initial type byte. - */ - - if (ssh->logctx) - ssh1_log_incoming_packet(ssh, st->pktin); - - st->pktin->savedpos = 0; - - crFinish(st->pktin); -} - -static void ssh2_log_incoming_packet(Ssh ssh, struct Packet *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - char *str; - int slen; - - pkt->savedpos = 0; - - if (ssh->logomitdata && - (pkt->type == SSH2_MSG_CHANNEL_DATA || - pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { - /* "Session data" packets - omit the data string. */ - ssh_pkt_getuint32(pkt); /* skip channel id */ - if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - ssh_pkt_getuint32(pkt); /* skip extended data type */ - blanks[nblanks].offset = pkt->savedpos + 4; - blanks[nblanks].type = PKTLOG_OMIT; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = slen; - nblanks++; - } - } - - log_packet(ssh->logctx, PKT_INCOMING, pkt->type, - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->type), - pkt->body, pkt->length, nblanks, blanks, &pkt->sequence, - 0, NULL); -} - -static void ssh2_log_outgoing_packet(Ssh ssh, struct Packet *pkt) -{ - int nblanks = 0; - struct logblank_t blanks[4]; - char *str; - int slen; - - /* - * For outgoing packets, pkt->length represents the length of the - * whole packet starting at pkt->data (including some header), and - * pkt->body refers to the point within that where the log-worthy - * payload begins. However, incoming packets expect pkt->length to - * represent only the payload length (that is, it's measured from - * pkt->body not from pkt->data). Temporarily adjust our outgoing - * packet to conform to the incoming-packet semantics, so that we - * can analyse it with the ssh_pkt_get functions. - */ - pkt->length -= (pkt->body - pkt->data); - pkt->savedpos = 0; - - if (ssh->logomitdata && - (pkt->type == SSH2_MSG_CHANNEL_DATA || - pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { - /* "Session data" packets - omit the data string. */ - ssh_pkt_getuint32(pkt); /* skip channel id */ - if (pkt->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - ssh_pkt_getuint32(pkt); /* skip extended data type */ - blanks[nblanks].offset = pkt->savedpos + 4; - blanks[nblanks].type = PKTLOG_OMIT; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = slen; - nblanks++; - } - } - - if (pkt->type == SSH2_MSG_USERAUTH_REQUEST && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* If this is a password packet, blank the password(s). */ - pkt->savedpos = 0; - ssh_pkt_getstring(pkt, &str, &slen); - ssh_pkt_getstring(pkt, &str, &slen); - ssh_pkt_getstring(pkt, &str, &slen); - if (slen == 8 && !memcmp(str, "password", 8)) { - ssh2_pkt_getbool(pkt); - /* Blank the password field. */ - blanks[nblanks].offset = pkt->savedpos; - blanks[nblanks].type = PKTLOG_BLANK; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; - nblanks++; - /* If there's another password field beyond it (change of - * password), blank that too. */ - ssh_pkt_getstring(pkt, &str, &slen); - if (str) - blanks[nblanks-1].len = - pkt->savedpos - blanks[nblanks].offset; - } - } - } else if (ssh->pkt_actx == SSH2_PKTCTX_KBDINTER && - pkt->type == SSH2_MSG_USERAUTH_INFO_RESPONSE && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* If this is a keyboard-interactive response packet, blank - * the responses. */ - pkt->savedpos = 0; - ssh_pkt_getuint32(pkt); - blanks[nblanks].offset = pkt->savedpos; - blanks[nblanks].type = PKTLOG_BLANK; - while (1) { - ssh_pkt_getstring(pkt, &str, &slen); - if (!str) - break; - } - blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; - nblanks++; - } else if (pkt->type == SSH2_MSG_CHANNEL_REQUEST && - conf_get_int(ssh->conf, CONF_logomitpass)) { - /* - * If this is an X forwarding request packet, blank the fake - * auth data. - * - * Note that while we blank the X authentication data here, we - * don't take any special action to blank the start of an X11 - * channel, so using MIT-MAGIC-COOKIE-1 and actually opening - * an X connection without having session blanking enabled is - * likely to leak your cookie into the log. - */ - pkt->savedpos = 0; - ssh_pkt_getuint32(pkt); - ssh_pkt_getstring(pkt, &str, &slen); - if (slen == 7 && !memcmp(str, "x11-req", 0)) { - ssh2_pkt_getbool(pkt); - ssh2_pkt_getbool(pkt); - ssh_pkt_getstring(pkt, &str, &slen); - blanks[nblanks].offset = pkt->savedpos; - blanks[nblanks].type = PKTLOG_BLANK; - ssh_pkt_getstring(pkt, &str, &slen); - if (str) { - blanks[nblanks].len = pkt->savedpos - blanks[nblanks].offset; - nblanks++; - } - } - } - - log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5], - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, pkt->data[5]), - pkt->body, pkt->length, nblanks, blanks, - &ssh->v2_outgoing_sequence, - pkt->downstream_id, pkt->additional_log_text); - - /* - * Undo the above adjustment of pkt->length, to put the packet - * back in the state we found it. - */ - pkt->length += (pkt->body - pkt->data); -} - -static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data, - int *datalen) -{ - struct rdpkt2_state_tag *st = &ssh->rdpkt2_state; - - crBegin(ssh->ssh2_rdpkt_crstate); - - st->pktin = ssh_new_packet(); - - st->pktin->type = 0; - st->pktin->length = 0; - if (ssh->sccipher) - st->cipherblk = ssh->sccipher->blksize; - else - st->cipherblk = 8; - if (st->cipherblk < 8) - st->cipherblk = 8; - st->maclen = ssh->scmac ? ssh->scmac->len : 0; - - if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) && - ssh->scmac && !ssh->scmac_etm) { - /* - * 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. - */ - - /* May as well allocate the whole lot now. */ - st->pktin->data = snewn(OUR_V2_PACKETLIMIT + st->maclen + APIEXTRA, - unsigned char); - - /* Read an amount corresponding to the MAC. */ - for (st->i = 0; st->i < st->maclen; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->i] = *(*data)++; - (*datalen)--; - } - - st->packetlen = 0; - { - unsigned char seq[4]; - ssh->scmac->start(ssh->sc_mac_ctx); - PUT_32BIT(seq, st->incoming_sequence); - ssh->scmac->bytes(ssh->sc_mac_ctx, seq, 4); - } - - for (;;) { /* Once around this loop per cipher block. */ - /* Read another cipher-block's worth, and tack it onto the end. */ - for (st->i = 0; st->i < st->cipherblk; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->packetlen+st->maclen+st->i] = *(*data)++; - (*datalen)--; - } - /* Decrypt one more block (a little further back in the stream). */ - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->pktin->data + st->packetlen, - st->cipherblk); - /* Feed that block to the MAC. */ - ssh->scmac->bytes(ssh->sc_mac_ctx, - st->pktin->data + st->packetlen, st->cipherblk); - st->packetlen += st->cipherblk; - /* See if that gives us a valid packet. */ - if (ssh->scmac->verresult(ssh->sc_mac_ctx, - st->pktin->data + st->packetlen) && - ((st->len = toint(GET_32BIT(st->pktin->data))) == - st->packetlen-4)) - break; - if (st->packetlen >= OUR_V2_PACKETLIMIT) { - bombout(("No valid incoming packet found")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - } - st->pktin->maxlen = st->packetlen + st->maclen; - st->pktin->data = sresize(st->pktin->data, - st->pktin->maxlen + APIEXTRA, - unsigned char); - } else if (ssh->scmac && ssh->scmac_etm) { - st->pktin->data = snewn(4 + APIEXTRA, unsigned char); - - /* - * OpenSSH encrypt-then-MAC mode: the packet length is - * unencrypted, unless the cipher supports length encryption. - */ - for (st->i = st->len = 0; st->i < 4; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->i] = *(*data)++; - (*datalen)--; - } - /* Cipher supports length decryption, so do it */ - if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { - /* Keep the packet the same though, so the MAC passes */ - unsigned char len[4]; - memcpy(len, st->pktin->data, 4); - ssh->sccipher->decrypt_length(ssh->sc_cipher_ctx, len, 4, st->incoming_sequence); - st->len = toint(GET_32BIT(len)); - } else { - st->len = toint(GET_32BIT(st->pktin->data)); - } - - /* - * _Completely_ silly lengths should be stomped on before they - * do us any more damage. - */ - if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT || - st->len % st->cipherblk != 0) { - bombout(("Incoming packet length field was garbled")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - /* - * So now we can work out the total packet length. - */ - st->packetlen = st->len + 4; - - /* - * Allocate memory for the rest of the packet. - */ - st->pktin->maxlen = st->packetlen + st->maclen; - st->pktin->data = sresize(st->pktin->data, - st->pktin->maxlen + APIEXTRA, - unsigned char); - - /* - * Read the remainder of the packet. - */ - for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->i] = *(*data)++; - (*datalen)--; - } - - /* - * Check the MAC. - */ - if (ssh->scmac - && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data, - st->len + 4, st->incoming_sequence)) { - bombout(("Incorrect MAC received on packet")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - /* Decrypt everything between the length field and the MAC. */ - if (ssh->sccipher) - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->pktin->data + 4, - st->packetlen - 4); - } else { - st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char); - - /* - * Acquire and decrypt the first block of the packet. This will - * contain the length and padding details. - */ - for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->i] = *(*data)++; - (*datalen)--; - } - - if (ssh->sccipher) - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->pktin->data, st->cipherblk); - - /* - * Now get the length figure. - */ - st->len = toint(GET_32BIT(st->pktin->data)); - - /* - * _Completely_ silly lengths should be stomped on before they - * do us any more damage. - */ - if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT || - (st->len + 4) % st->cipherblk != 0) { - bombout(("Incoming packet was garbled on decryption")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - - /* - * So now we can work out the total packet length. - */ - st->packetlen = st->len + 4; - - /* - * Allocate memory for the rest of the packet. - */ - st->pktin->maxlen = st->packetlen + st->maclen; - st->pktin->data = sresize(st->pktin->data, - st->pktin->maxlen + APIEXTRA, - unsigned char); - - /* - * Read and decrypt the remainder of the packet. - */ - for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen; - st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->i] = *(*data)++; - (*datalen)--; - } - /* Decrypt everything _except_ the MAC. */ - if (ssh->sccipher) - ssh->sccipher->decrypt(ssh->sc_cipher_ctx, - st->pktin->data + st->cipherblk, - st->packetlen - st->cipherblk); - - /* - * Check the MAC. - */ - if (ssh->scmac - && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data, - st->len + 4, st->incoming_sequence)) { - bombout(("Incorrect MAC received on packet")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - } - /* Get and sanity-check the amount of random padding. */ - st->pad = st->pktin->data[4]; - if (st->pad < 4 || st->len - st->pad < 1) { - bombout(("Invalid padding length on received packet")); - ssh_free_packet(st->pktin); - crStop(NULL); - } - /* - * This enables us to deduce the payload length. - */ - st->payload = st->len - st->pad - 1; - - st->pktin->length = st->payload + 5; - st->pktin->encrypted_len = st->packetlen; - - st->pktin->sequence = st->incoming_sequence++; - - st->pktin->length = st->packetlen - st->pad; - assert(st->pktin->length >= 0); - - /* - * Decompress packet payload. - */ - { - unsigned char *newpayload; - int newlen; - if (ssh->sccomp && - ssh->sccomp->decompress(ssh->sc_comp_ctx, - st->pktin->data + 5, st->pktin->length - 5, - &newpayload, &newlen)) { - if (st->pktin->maxlen < newlen + 5) { - st->pktin->maxlen = newlen + 5; - st->pktin->data = sresize(st->pktin->data, - st->pktin->maxlen + APIEXTRA, - unsigned char); - } - st->pktin->length = 5 + newlen; - memcpy(st->pktin->data + 5, newpayload, newlen); - sfree(newpayload); - } - } - - /* - * RFC 4253 doesn't explicitly say that completely empty packets - * with no type byte are forbidden, so treat them as deserving - * an SSH_MSG_UNIMPLEMENTED. - */ - if (st->pktin->length <= 5) { /* == 5 we hope, but robustness */ - ssh2_msg_something_unimplemented(ssh, st->pktin); - crStop(NULL); - } - /* - * pktin->body and pktin->length should identify the semantic - * content of the packet, excluding the initial type byte. - */ - st->pktin->type = st->pktin->data[5]; - st->pktin->body = st->pktin->data + 6; - st->pktin->length -= 6; - assert(st->pktin->length >= 0); /* one last double-check */ - - if (ssh->logctx) - ssh2_log_incoming_packet(ssh, st->pktin); - - st->pktin->savedpos = 0; - - crFinish(st->pktin); -} - -static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, - const unsigned char **data, - int *datalen) -{ - struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state; - - crBegin(ssh->ssh2_bare_rdpkt_crstate); - - /* - * Read the packet length field. - */ - for (st->i = 0; st->i < 4; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->length[st->i] = *(*data)++; - (*datalen)--; - } - - st->packetlen = toint(GET_32BIT_MSB_FIRST(st->length)); - if (st->packetlen <= 0 || st->packetlen >= OUR_V2_PACKETLIMIT) { - bombout(("Invalid packet length received")); - crStop(NULL); - } - - st->pktin = ssh_new_packet(); - st->pktin->data = snewn(st->packetlen, unsigned char); - - st->pktin->encrypted_len = st->packetlen; - - st->pktin->sequence = st->incoming_sequence++; - - /* - * Read the remainder of the packet. - */ - for (st->i = 0; st->i < st->packetlen; st->i++) { - while ((*datalen) == 0) - crReturn(NULL); - st->pktin->data[st->i] = *(*data)++; - (*datalen)--; - } - - /* - * pktin->body and pktin->length should identify the semantic - * content of the packet, excluding the initial type byte. - */ - st->pktin->type = st->pktin->data[0]; - st->pktin->body = st->pktin->data + 1; - st->pktin->length = st->packetlen - 1; - - /* - * Log incoming packet, possibly omitting sensitive fields. - */ - if (ssh->logctx) - ssh2_log_incoming_packet(ssh, st->pktin); - - st->pktin->savedpos = 0; - - crFinish(st->pktin); -} - -static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p) -{ - int pad, biglen, i, pktoffs; - unsigned long crc; -#ifdef __SC__ - /* - * XXX various versions of SC (including 8.8.4) screw up the - * register allocation in this function and use the same register - * (D6) for len and as a temporary, with predictable results. The - * following sledgehammer prevents this. - */ - volatile -#endif - int len; - - if (ssh->logctx) - ssh1_log_outgoing_packet(ssh, pkt); - - if (ssh->v1_compressing) { - unsigned char *compblk; - int complen; - zlib_compress_block(ssh->cs_comp_ctx, - pkt->data + 12, pkt->length - 12, - &compblk, &complen); - ssh_pkt_ensure(pkt, complen + 2); /* just in case it's got bigger */ - memcpy(pkt->data + 12, compblk, complen); - sfree(compblk); - pkt->length = complen + 12; - } - - ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */ - pkt->length += 4; - len = pkt->length - 4 - 8; /* len(type+data+CRC) */ - pad = 8 - (len % 8); - pktoffs = 8 - pad; - biglen = len + pad; /* len(padding+type+data+CRC) */ - - for (i = pktoffs; i < 4+8; i++) - pkt->data[i] = random_byte(); - crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */ - PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc); - PUT_32BIT(pkt->data + pktoffs, len); - - if (ssh->cipher) - ssh->cipher->encrypt(ssh->v1_cipher_ctx, - pkt->data + pktoffs + 4, biglen); - - if (offset_p) *offset_p = pktoffs; - return biglen + 4; /* len(length+padding+type+data+CRC) */ -} - -static int s_write(Ssh ssh, void *data, int len) -{ - if (ssh->logctx) - log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); - if (!ssh->s) - return 0; - return sk_write(ssh->s, (char *)data, len); -} - -static void s_wrpkt(Ssh ssh, struct Packet *pkt) -{ - int len, backlog, offset; - len = s_wrpkt_prepare(ssh, pkt, &offset); - backlog = s_write(ssh, pkt->data + offset, len); - if (backlog > SSH_MAX_BACKLOG) - ssh_throttle_all(ssh, 1, backlog); - ssh_free_packet(pkt); -} - -static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt) -{ - int len, offset; - len = s_wrpkt_prepare(ssh, pkt, &offset); - if (ssh->deferred_len + len > ssh->deferred_size) { - ssh->deferred_size = ssh->deferred_len + len + 128; - ssh->deferred_send_data = sresize(ssh->deferred_send_data, - ssh->deferred_size, - unsigned char); - } - memcpy(ssh->deferred_send_data + ssh->deferred_len, - pkt->data + offset, len); - ssh->deferred_len += len; - ssh_free_packet(pkt); -} - -/* - * Construct a SSH-1 packet with the specified contents. - * (This all-at-once interface used to be the only one, but now SSH-1 - * packets can also be constructed incrementally.) - */ -static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap) -{ - int argtype; - Bignum bn; - struct Packet *pkt; - - pkt = ssh1_pkt_init(pkttype); - - while ((argtype = va_arg(ap, int)) != PKT_END) { - unsigned char *argp, argchar; - char *sargp; - unsigned long argint; - int arglen; - switch (argtype) { - /* Actual fields in the packet */ - case PKT_INT: - argint = va_arg(ap, int); - ssh_pkt_adduint32(pkt, argint); - break; - case PKT_CHAR: - argchar = (unsigned char) va_arg(ap, int); - ssh_pkt_addbyte(pkt, argchar); - break; - case PKT_DATA: - argp = va_arg(ap, unsigned char *); - arglen = va_arg(ap, int); - ssh_pkt_adddata(pkt, argp, arglen); - break; - case PKT_STR: - sargp = va_arg(ap, char *); - ssh_pkt_addstring(pkt, sargp); - break; - case PKT_BIGNUM: - bn = va_arg(ap, Bignum); - ssh1_pkt_addmp(pkt, bn); - break; - } - } - - return pkt; -} - -static void send_packet(Ssh ssh, int pkttype, ...) -{ - struct Packet *pkt; - va_list ap; - va_start(ap, pkttype); - pkt = construct_packet(ssh, pkttype, ap); - va_end(ap); - s_wrpkt(ssh, pkt); -} - -static void defer_packet(Ssh ssh, int pkttype, ...) -{ - struct Packet *pkt; - va_list ap; - va_start(ap, pkttype); - pkt = construct_packet(ssh, pkttype, ap); - va_end(ap); - s_wrpkt_defer(ssh, pkt); -} - -static int ssh_versioncmp(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; -} - -/* - * Utility routines for putting an SSH-protocol `string' and - * `uint32' into a hash state. - */ -static void hash_string(const struct ssh_hash *h, void *s, void *str, int len) -{ - unsigned char lenblk[4]; - PUT_32BIT(lenblk, len); - h->bytes(s, lenblk, 4); - h->bytes(s, str, len); -} - -static void hash_uint32(const struct ssh_hash *h, void *s, unsigned i) -{ - unsigned char intblk[4]; - PUT_32BIT(intblk, i); - h->bytes(s, intblk, 4); -} - -/* - * Packet construction functions. Mostly shared between SSH-1 and SSH-2. - */ -static void ssh_pkt_ensure(struct Packet *pkt, int length) -{ - if (pkt->maxlen < length) { - unsigned char *body = pkt->body; - int offset = body ? body - pkt->data : 0; - pkt->maxlen = length + 256; - pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char); - if (body) pkt->body = pkt->data + offset; - } -} -static void ssh_pkt_adddata(struct Packet *pkt, const void *data, int len) -{ - pkt->length += len; - ssh_pkt_ensure(pkt, pkt->length); - memcpy(pkt->data + pkt->length - len, data, len); -} -static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte) -{ - ssh_pkt_adddata(pkt, &byte, 1); -} -static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value) -{ - ssh_pkt_adddata(pkt, &value, 1); -} -static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value) -{ - unsigned char x[4]; - PUT_32BIT(x, value); - ssh_pkt_adddata(pkt, x, 4); -} -static void ssh_pkt_addstring_start(struct Packet *pkt) -{ - ssh_pkt_adduint32(pkt, 0); - pkt->savedpos = pkt->length; -} -static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data, - int len) -{ - ssh_pkt_adddata(pkt, data, len); - PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos); -} -static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data) -{ - ssh_pkt_addstring_data(pkt, data, strlen(data)); -} -static void ssh_pkt_addstring(struct Packet *pkt, const char *data) -{ - ssh_pkt_addstring_start(pkt); - ssh_pkt_addstring_str(pkt, data); -} -static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b) -{ - int len = ssh1_bignum_length(b); - unsigned char *data = snewn(len, unsigned char); - (void) ssh1_write_bignum(data, b); - ssh_pkt_adddata(pkt, data, len); - sfree(data); -} -static unsigned char *ssh2_mpint_fmt(Bignum b, int *len) -{ - unsigned char *p; - int i, n = (bignum_bitcount(b) + 7) / 8; - p = snewn(n + 1, unsigned char); - p[0] = 0; - for (i = 1; i <= n; i++) - p[i] = bignum_byte(b, n - i); - i = 0; - while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0) - i++; - memmove(p, p + i, n + 1 - i); - *len = n + 1 - i; - return p; -} -static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b) -{ - unsigned char *p; - int len; - p = ssh2_mpint_fmt(b, &len); - ssh_pkt_addstring_start(pkt); - ssh_pkt_addstring_data(pkt, (char *)p, len); - sfree(p); -} - -static struct Packet *ssh1_pkt_init(int pkt_type) -{ - struct Packet *pkt = ssh_new_packet(); - pkt->length = 4 + 8; /* space for length + max padding */ - ssh_pkt_addbyte(pkt, pkt_type); - pkt->body = pkt->data + pkt->length; - pkt->type = pkt_type; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - return pkt; -} - -/* For legacy code (SSH-1 and -2 packet construction used to be separate) */ -#define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length) -#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len) -#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte) -#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value) -#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt) -#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data) -#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len) -#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data) - -static struct Packet *ssh2_pkt_init(int pkt_type) -{ - struct Packet *pkt = ssh_new_packet(); - pkt->length = 5; /* space for packet length + padding length */ - pkt->forcepad = 0; - pkt->type = pkt_type; - ssh_pkt_addbyte(pkt, (unsigned char) pkt_type); - pkt->body = pkt->data + pkt->length; /* after packet type */ - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - return pkt; -} - -/* - * Construct an SSH-2 final-form packet: compress it, encrypt it, - * put the MAC on it. Final packet, ready to be sent, is stored in - * pkt->data. Total length is returned. - */ -static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt) -{ - int cipherblk, maclen, padding, unencrypted_prefix, i; - - if (ssh->logctx) - ssh2_log_outgoing_packet(ssh, pkt); - - if (ssh->bare_connection) { - /* - * Trivial packet construction for the bare connection - * protocol. - */ - PUT_32BIT(pkt->data + 1, pkt->length - 5); - pkt->body = pkt->data + 1; - ssh->v2_outgoing_sequence++; /* only for diagnostics, really */ - return pkt->length - 1; - } - - /* - * Compress packet payload. - */ - { - unsigned char *newpayload; - int newlen; - if (ssh->cscomp && - ssh->cscomp->compress(ssh->cs_comp_ctx, pkt->data + 5, - pkt->length - 5, - &newpayload, &newlen)) { - pkt->length = 5; - ssh2_pkt_adddata(pkt, newpayload, newlen); - sfree(newpayload); - } - } - - /* - * Add padding. At least four bytes, and must also bring total - * length (minus MAC) up to a multiple of the block size. - * If pkt->forcepad is set, make sure the packet is at least that size - * after padding. - */ - cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8; /* block size */ - cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ - padding = 4; - unencrypted_prefix = (ssh->csmac && ssh->csmac_etm) ? 4 : 0; - if (pkt->length + padding < pkt->forcepad) - padding = pkt->forcepad - pkt->length; - padding += - (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) - % cipherblk; - assert(padding <= 255); - maclen = ssh->csmac ? ssh->csmac->len : 0; - ssh2_pkt_ensure(pkt, pkt->length + padding + maclen); - pkt->data[4] = padding; - for (i = 0; i < padding; i++) - pkt->data[pkt->length + i] = random_byte(); - PUT_32BIT(pkt->data, pkt->length + padding - 4); - - /* Encrypt length if the scheme requires it */ - if (ssh->cscipher && (ssh->cscipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) { - ssh->cscipher->encrypt_length(ssh->cs_cipher_ctx, pkt->data, 4, - ssh->v2_outgoing_sequence); - } - - if (ssh->csmac && ssh->csmac_etm) { - /* - * OpenSSH-defined encrypt-then-MAC protocol. - */ - if (ssh->cscipher) - ssh->cscipher->encrypt(ssh->cs_cipher_ctx, - pkt->data + 4, pkt->length + padding - 4); - ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, - pkt->length + padding, - ssh->v2_outgoing_sequence); - } else { - /* - * SSH-2 standard protocol. - */ - if (ssh->csmac) - ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data, - pkt->length + padding, - ssh->v2_outgoing_sequence); - if (ssh->cscipher) - ssh->cscipher->encrypt(ssh->cs_cipher_ctx, - pkt->data, pkt->length + padding); - } - - ssh->v2_outgoing_sequence++; /* whether or not we MACed */ - pkt->encrypted_len = pkt->length + padding; - - /* Ready-to-send packet starts at pkt->data. We return length. */ - pkt->body = pkt->data; - return pkt->length + padding + maclen; -} - -/* - * Routines called from the main SSH code to send packets. There - * are quite a few of these, because we have two separate - * mechanisms for delaying the sending of packets: - * - * - In order to send an IGNORE message and a password message in - * a single fixed-length blob, we require the ability to - * concatenate the encrypted forms of those two packets _into_ a - * single blob and then pass it to our transport - * layer in one go. Hence, there's a deferment mechanism which - * works after packet encryption. - * - * - In order to avoid sending any connection-layer messages - * during repeat key exchange, we have to queue up any such - * outgoing messages _before_ they are encrypted (and in - * particular before they're allocated sequence numbers), and - * then send them once we've finished. - * - * I call these mechanisms `defer' and `queue' respectively, so as - * to distinguish them reasonably easily. - * - * The functions send_noqueue() and defer_noqueue() free the packet - * structure they are passed. Every outgoing packet goes through - * precisely one of these functions in its life; packets passed to - * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of - * these or get queued, and then when the queue is later emptied - * the packets are all passed to defer_noqueue(). - * - * When using a CBC-mode cipher, it's necessary to ensure that an - * attacker can't provide data to be encrypted using an IV that they - * know. We ensure this by prefixing each packet that might contain - * user data with an SSH_MSG_IGNORE. This is done using the deferral - * mechanism, so in this case send_noqueue() ends up redirecting to - * defer_noqueue(). If you don't like this inefficiency, don't use - * CBC. - */ - -static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int); -static void ssh_pkt_defersend(Ssh); - -/* - * Send an SSH-2 packet immediately, without queuing or deferring. - */ -static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt) -{ - int len; - int backlog; - if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) { - /* We need to send two packets, so use the deferral mechanism. */ - ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); - ssh_pkt_defersend(ssh); - return; - } - len = ssh2_pkt_construct(ssh, pkt); - backlog = s_write(ssh, pkt->body, len); - if (backlog > SSH_MAX_BACKLOG) - ssh_throttle_all(ssh, 1, backlog); - - ssh->outgoing_data_size += pkt->encrypted_len; - if (!ssh->kex_in_progress && - !ssh->bare_connection && - ssh->max_data_size != 0 && - ssh->outgoing_data_size > ssh->max_data_size) - do_ssh2_transport(ssh, "too much data sent", -1, NULL); - - ssh_free_packet(pkt); -} - -/* - * Defer an SSH-2 packet. - */ -static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore) -{ - int len; - if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) && - ssh->deferred_len == 0 && !noignore && - !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - /* - * Interpose an SSH_MSG_IGNORE to ensure that user data don't - * get encrypted with a known IV. - */ - struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE); - ssh2_pkt_addstring_start(ipkt); - ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE); - } - len = ssh2_pkt_construct(ssh, pkt); - if (ssh->deferred_len + len > ssh->deferred_size) { - ssh->deferred_size = ssh->deferred_len + len + 128; - ssh->deferred_send_data = sresize(ssh->deferred_send_data, - ssh->deferred_size, - unsigned char); - } - memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->body, len); - ssh->deferred_len += len; - ssh->deferred_data_size += pkt->encrypted_len; - ssh_free_packet(pkt); -} - -/* - * Queue an SSH-2 packet. - */ -static void ssh2_pkt_queue(Ssh ssh, struct Packet *pkt) -{ - assert(ssh->queueing); - - if (ssh->queuelen >= ssh->queuesize) { - ssh->queuesize = ssh->queuelen + 32; - ssh->queue = sresize(ssh->queue, ssh->queuesize, struct Packet *); - } - - ssh->queue[ssh->queuelen++] = pkt; -} - -/* - * Either queue or send a packet, depending on whether queueing is - * set. - */ -static void ssh2_pkt_send(Ssh ssh, struct Packet *pkt) -{ - if (ssh->queueing) - ssh2_pkt_queue(ssh, pkt); - else - ssh2_pkt_send_noqueue(ssh, pkt); -} - -/* - * Either queue or defer a packet, depending on whether queueing is - * set. - */ -static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt) -{ - if (ssh->queueing) - ssh2_pkt_queue(ssh, pkt); - else - ssh2_pkt_defer_noqueue(ssh, pkt, FALSE); -} - -/* - * Send the whole deferred data block constructed by - * ssh2_pkt_defer() or SSH-1's defer_packet(). - * - * The expected use of the defer mechanism is that you call - * ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If - * not currently queueing, this simply sets up deferred_send_data - * and then sends it. If we _are_ currently queueing, the calls to - * ssh2_pkt_defer() put the deferred packets on to the queue - * instead, and therefore ssh_pkt_defersend() has no deferred data - * to send. Hence, there's no need to make it conditional on - * ssh->queueing. - */ -static void ssh_pkt_defersend(Ssh ssh) -{ - int backlog; - backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len); - ssh->deferred_len = ssh->deferred_size = 0; - sfree(ssh->deferred_send_data); - ssh->deferred_send_data = NULL; - if (backlog > SSH_MAX_BACKLOG) - ssh_throttle_all(ssh, 1, backlog); - - if (ssh->version == 2) { - ssh->outgoing_data_size += ssh->deferred_data_size; - ssh->deferred_data_size = 0; - if (!ssh->kex_in_progress && - !ssh->bare_connection && - ssh->max_data_size != 0 && - ssh->outgoing_data_size > ssh->max_data_size) - do_ssh2_transport(ssh, "too much data sent", -1, NULL); - } -} - -/* - * Send a packet whose length needs to be disguised (typically - * passwords or keyboard-interactive responses). - */ -static void ssh2_pkt_send_with_padding(Ssh ssh, struct Packet *pkt, - int padsize) -{ -#if 0 - if (0) { - /* - * The simplest way to do this is to adjust the - * variable-length padding field in the outgoing packet. - * - * Currently compiled out, because some Cisco SSH servers - * don't like excessively padded packets (bah, why's it - * always Cisco?) - */ - pkt->forcepad = padsize; - ssh2_pkt_send(ssh, pkt); - } else -#endif - { - /* - * If we can't do that, however, an alternative approach is - * to use the pkt_defer mechanism to bundle the packet - * tightly together with an SSH_MSG_IGNORE such that their - * combined length is a constant. So first we construct the - * final form of this packet and defer its sending. - */ - ssh2_pkt_defer(ssh, pkt); - - /* - * Now construct an SSH_MSG_IGNORE which includes a string - * that's an exact multiple of the cipher block size. (If - * the cipher is NULL so that the block size is - * unavailable, we don't do this trick at all, because we - * gain nothing by it.) - */ - if (ssh->cscipher && - !(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - int stringlen, i; - - stringlen = (256 - ssh->deferred_len); - stringlen += ssh->cscipher->blksize - 1; - stringlen -= (stringlen % ssh->cscipher->blksize); - if (ssh->cscomp) { - /* - * Temporarily disable actual compression, so we - * can guarantee to get this string exactly the - * length we want it. The compression-disabling - * routine should return an integer indicating how - * many bytes we should adjust our string length - * by. - */ - stringlen -= - ssh->cscomp->disable_compression(ssh->cs_comp_ctx); - } - pkt = ssh2_pkt_init(SSH2_MSG_IGNORE); - ssh2_pkt_addstring_start(pkt); - for (i = 0; i < stringlen; i++) { - char c = (char) random_byte(); - ssh2_pkt_addstring_data(pkt, &c, 1); - } - ssh2_pkt_defer(ssh, pkt); - } - ssh_pkt_defersend(ssh); - } -} - -/* - * Send all queued SSH-2 packets. We send them by means of - * ssh2_pkt_defer_noqueue(), in case they included a pair of - * packets that needed to be lumped together. - */ -static void ssh2_pkt_queuesend(Ssh ssh) -{ - int i; - - assert(!ssh->queueing); - - for (i = 0; i < ssh->queuelen; i++) - ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE); - ssh->queuelen = 0; - - ssh_pkt_defersend(ssh); -} - -#if 0 -void bndebug(char *string, Bignum b) -{ - unsigned char *p; - int i, len; - p = ssh2_mpint_fmt(b, &len); - debug(("%s", string)); - for (i = 0; i < len; i++) - debug((" %02x", p[i])); - debug(("\n")); - sfree(p); -} -#endif - -static void hash_mpint(const struct ssh_hash *h, void *s, Bignum b) -{ - unsigned char *p; - int len; - p = ssh2_mpint_fmt(b, &len); - hash_string(h, s, p, len); - sfree(p); -} - -/* - * Packet decode functions for both SSH-1 and SSH-2. - */ -static unsigned long ssh_pkt_getuint32(struct Packet *pkt) -{ - unsigned long value; - if (pkt->length - pkt->savedpos < 4) - return 0; /* arrgh, no way to decline (FIXME?) */ - value = GET_32BIT(pkt->body + pkt->savedpos); - pkt->savedpos += 4; - return value; -} -static int ssh2_pkt_getbool(struct Packet *pkt) -{ - unsigned long value; - if (pkt->length - pkt->savedpos < 1) - return 0; /* arrgh, no way to decline (FIXME?) */ - value = pkt->body[pkt->savedpos] != 0; - pkt->savedpos++; - return value; -} -static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length) -{ - int len; - *p = NULL; - *length = 0; - if (pkt->length - pkt->savedpos < 4) - return; - len = toint(GET_32BIT(pkt->body + pkt->savedpos)); - if (len < 0) - return; - *length = len; - pkt->savedpos += 4; - if (pkt->length - pkt->savedpos < *length) - return; - *p = (char *)(pkt->body + pkt->savedpos); - pkt->savedpos += *length; -} -static void *ssh_pkt_getdata(struct Packet *pkt, int length) -{ - if (pkt->length - pkt->savedpos < length) - return NULL; - pkt->savedpos += length; - return pkt->body + (pkt->savedpos - length); -} -static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key, - const unsigned char **keystr) -{ - int j; - - j = makekey(pkt->body + pkt->savedpos, - pkt->length - pkt->savedpos, - key, keystr, 0); - - if (j < 0) - return FALSE; - - pkt->savedpos += j; - assert(pkt->savedpos < pkt->length); - - return TRUE; -} -static Bignum ssh1_pkt_getmp(struct Packet *pkt) -{ - int j; - Bignum b; - - j = ssh1_read_bignum(pkt->body + pkt->savedpos, - pkt->length - pkt->savedpos, &b); - - if (j < 0) - return NULL; - - pkt->savedpos += j; - return b; -} -static Bignum ssh2_pkt_getmp(struct Packet *pkt) -{ - char *p; - int length; - Bignum b; - - ssh_pkt_getstring(pkt, &p, &length); - if (!p) - return NULL; - if (p[0] & 0x80) - return NULL; - b = bignum_from_bytes((unsigned char *)p, length); - return b; -} - -/* - * Helper function to add an SSH-2 signature blob to a packet. - * Expects to be shown the public key blob as well as the signature - * blob. Normally works just like ssh2_pkt_addstring, but will - * fiddle with the signature packet if necessary for - * BUG_SSH2_RSA_PADDING. - */ -static void ssh2_add_sigblob(Ssh ssh, struct Packet *pkt, - void *pkblob_v, int pkblob_len, - void *sigblob_v, int sigblob_len) -{ - unsigned char *pkblob = (unsigned char *)pkblob_v; - unsigned char *sigblob = (unsigned char *)sigblob_v; - - /* dmemdump(pkblob, pkblob_len); */ - /* dmemdump(sigblob, sigblob_len); */ - - /* - * See if this is in fact an ssh-rsa signature and a buggy - * server; otherwise we can just do this the easy way. - */ - if ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) && pkblob_len > 4+7+4 && - (GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) { - int pos, len, siglen; - - /* - * Find the byte length of the modulus. - */ - - pos = 4+7; /* skip over "ssh-rsa" */ - len = toint(GET_32BIT(pkblob+pos)); /* get length of exponent */ - if (len < 0 || len > pkblob_len - pos - 4) - goto give_up; - pos += 4 + len; /* skip over exponent */ - if (pkblob_len - pos < 4) - goto give_up; - len = toint(GET_32BIT(pkblob+pos)); /* find length of modulus */ - if (len < 0 || len > pkblob_len - pos - 4) - goto give_up; - pos += 4; /* find modulus itself */ - while (len > 0 && pkblob[pos] == 0) - len--, pos++; - /* debug(("modulus length is %d\n", len)); */ - - /* - * Now find the signature integer. - */ - pos = 4+7; /* skip over "ssh-rsa" */ - if (sigblob_len < pos+4) - goto give_up; - siglen = toint(GET_32BIT(sigblob+pos)); - if (siglen != sigblob_len - pos - 4) - goto give_up; - /* debug(("signature length is %d\n", siglen)); */ - - if (len != siglen) { - unsigned char newlen[4]; - ssh2_pkt_addstring_start(pkt); - ssh2_pkt_addstring_data(pkt, (char *)sigblob, pos); - /* dmemdump(sigblob, pos); */ - pos += 4; /* point to start of actual sig */ - PUT_32BIT(newlen, len); - ssh2_pkt_addstring_data(pkt, (char *)newlen, 4); - /* dmemdump(newlen, 4); */ - newlen[0] = 0; - while (len-- > siglen) { - ssh2_pkt_addstring_data(pkt, (char *)newlen, 1); - /* dmemdump(newlen, 1); */ - } - ssh2_pkt_addstring_data(pkt, (char *)(sigblob+pos), siglen); - /* dmemdump(sigblob+pos, siglen); */ - return; - } - - /* Otherwise fall through and do it the easy way. We also come - * here as a fallback if we discover above that the key blob - * is misformatted in some way. */ - give_up:; - } - - ssh2_pkt_addstring_start(pkt); - ssh2_pkt_addstring_data(pkt, (char *)sigblob, sigblob_len); -} - -/* - * Examine the remote side's version string and compare it against - * a list of known buggy implementations. - */ -static void ssh_detect_bugs(Ssh ssh, char *vstring) -{ - char *imp; /* pointer to implementation part */ - imp = vstring; - imp += strcspn(imp, "-"); - if (*imp) imp++; - imp += strcspn(imp, "-"); - if (*imp) imp++; - - ssh->remote_bugs = 0; - - /* - * General notes on server version strings: - * - Not all servers reporting "Cisco-1.25" have all the bugs listed - * here -- in particular, we've heard of one that's perfectly happy - * with SSH1_MSG_IGNOREs -- but this string never seems to change, - * so we can't distinguish them. - */ - if (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_ignore1) == AUTO && - (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") || - !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") || - !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") || - !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) { - /* - * These versions don't support SSH1_MSG_IGNORE, so we have - * to use a different defence against password length - * sniffing. - */ - ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; - logevent("We believe remote version has SSH-1 ignore bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_plainpw1) == AUTO && - (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) { - /* - * These versions need a plain password sent; they can't - * handle having a null and a random length of data after - * the password. - */ - ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; - logevent("We believe remote version needs a plain SSH-1 password"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_rsa1) == AUTO && - (!strcmp(imp, "Cisco-1.25")))) { - /* - * These versions apparently have no clue whatever about - * RSA authentication and will panic and die if they see - * an AUTH_RSA message. - */ - ssh->remote_bugs |= BUG_CHOKES_ON_RSA; - logevent("We believe remote version can't handle SSH-1 RSA authentication"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_hmac2) == AUTO && - !wc_match("* VShell", imp) && - (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) || - wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) || - wc_match("2.1 *", imp)))) { - /* - * These versions have the HMAC bug. - */ - ssh->remote_bugs |= BUG_SSH2_HMAC; - logevent("We believe remote version has SSH-2 HMAC bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_derivekey2) == AUTO && - !wc_match("* VShell", imp) && - (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) { - /* - * These versions have the key-derivation bug (failing to - * include the literal shared secret in the hashes that - * generate the keys). - */ - ssh->remote_bugs |= BUG_SSH2_DERIVEKEY; - logevent("We believe remote version has SSH-2 key-derivation bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO && - (wc_match("OpenSSH_2.[5-9]*", imp) || - wc_match("OpenSSH_3.[0-2]*", imp) || - 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. - */ - ssh->remote_bugs |= BUG_SSH2_RSA_PADDING; - logevent("We believe remote version has SSH-2 RSA padding bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_pksessid2) == AUTO && - wc_match("OpenSSH_2.[0-2]*", imp))) { - /* - * These versions have the SSH-2 session-ID bug in - * public-key authentication. - */ - ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID; - logevent("We believe remote version has SSH-2 public-key-session-ID bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_rekey2) == AUTO && - (wc_match("DigiSSH_2.0", imp) || - wc_match("OpenSSH_2.[0-4]*", imp) || - wc_match("OpenSSH_2.5.[0-3]*", imp) || - wc_match("Sun_SSH_1.0", imp) || - wc_match("Sun_SSH_1.0.1", imp) || - /* All versions <= 1.2.6 (they changed their format in 1.2.7) */ - wc_match("WeOnlyDo-*", imp)))) { - /* - * These versions have the SSH-2 rekey bug. - */ - ssh->remote_bugs |= BUG_SSH2_REKEY; - logevent("We believe remote version has SSH-2 rekey bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == FORCE_ON || - (conf_get_int(ssh->conf, CONF_sshbug_maxpkt2) == AUTO && - (wc_match("1.36_sshlib GlobalSCAPE", imp) || - wc_match("1.36 sshlib: GlobalScape", imp)))) { - /* - * This version ignores our makpkt and needs to be throttled. - */ - ssh->remote_bugs |= BUG_SSH2_MAXPKT; - logevent("We believe remote version ignores SSH-2 maximum packet size"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_ignore2) == FORCE_ON) { - /* - * Servers that don't support SSH2_MSG_IGNORE. Currently, - * none detected automatically. - */ - ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; - logevent("We believe remote version has SSH-2 ignore bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_oldgex2) == FORCE_ON || - (conf_get_int(ssh->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. - */ - ssh->remote_bugs |= BUG_SSH2_OLDGEX; - logevent("We believe remote version has outdated SSH-2 GEX"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) { - /* - * Servers that don't support our winadj request for one - * reason or another. Currently, none detected automatically. - */ - ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ; - logevent("We believe remote version has winadj bug"); - } - - if (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == FORCE_ON || - (conf_get_int(ssh->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 - */ - ssh->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; - logevent("We believe remote version has SSH-2 channel request bug"); - } -} - -/* - * The `software version' part of an SSH version string is required - * to contain no spaces or minus signs. - */ -static void ssh_fix_verstring(char *str) -{ - /* Eat "-". */ - while (*str && *str != '-') str++; - assert(*str == '-'); str++; - - /* Convert minus signs and spaces in the remaining string into - * underscores. */ - while (*str) { - if (*str == '-' || *str == ' ') - *str = '_'; - str++; - } -} - -/* - * Send an appropriate SSH version string. - */ -static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers) -{ - char *verstring; - - if (ssh->version == 2) { - /* - * Construct a v2 version string. - */ - verstring = dupprintf("%s2.0-%s\015\012", protoname, sshver); - } else { - /* - * Construct a v1 version string. - */ - assert(!strcmp(protoname, "SSH-")); /* no v1 bare connection protocol */ - verstring = dupprintf("SSH-%s-%s\012", - (ssh_versioncmp(svers, "1.5") <= 0 ? - svers : "1.5"), - sshver); - } - - ssh_fix_verstring(verstring + strlen(protoname)); -#ifdef FUZZING - /* FUZZING make PuTTY insecure, so make live use difficult. */ - verstring[0] = 'I'; -#endif - - if (ssh->version == 2) { - size_t len; - /* - * Record our version string. - */ - len = strcspn(verstring, "\015\012"); - ssh->v_c = snewn(len + 1, char); - memcpy(ssh->v_c, verstring, len); - ssh->v_c[len] = 0; - } - - logeventf(ssh, "We claim version: %.*s", - strcspn(verstring, "\015\012"), verstring); - s_write(ssh, verstring, strlen(verstring)); - sfree(verstring); -} - -static int do_ssh_init(Ssh ssh, unsigned char c) -{ - static const char protoname[] = "SSH-"; - - struct do_ssh_init_state { - int crLine; - int vslen; - char version[10]; - char *vstring; - int vstrsize; - int i; - int proto1, proto2; - }; - crState(do_ssh_init_state); - - crBeginState; - - /* Search for a line beginning with the protocol name prefix in - * the input. */ - for (;;) { - for (s->i = 0; protoname[s->i]; s->i++) { - if ((char)c != protoname[s->i]) goto no; - crReturn(1); - } - break; - no: - while (c != '\012') - crReturn(1); - crReturn(1); - } - - ssh->session_started = TRUE; - - s->vstrsize = sizeof(protoname) + 16; - s->vstring = snewn(s->vstrsize, char); - strcpy(s->vstring, protoname); - s->vslen = strlen(protoname); - s->i = 0; - while (1) { - if (s->vslen >= s->vstrsize - 1) { - s->vstrsize += 16; - s->vstring = sresize(s->vstring, s->vstrsize, char); - } - s->vstring[s->vslen++] = c; - if (s->i >= 0) { - if (c == '-') { - s->version[s->i] = '\0'; - s->i = -1; - } else if (s->i < sizeof(s->version) - 1) - s->version[s->i++] = c; - } else if (c == '\012') - break; - crReturn(1); /* get another char */ - } - - ssh->agentfwd_enabled = FALSE; - ssh->rdpkt2_state.incoming_sequence = 0; - - s->vstring[s->vslen] = 0; - s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ - logeventf(ssh, "Server version: %s", s->vstring); - ssh_detect_bugs(ssh, s->vstring); - - /* - * Decide which SSH protocol version to support. - */ - - /* Anything strictly below "2.0" means protocol 1 is supported. */ - s->proto1 = ssh_versioncmp(s->version, "2.0") < 0; - /* Anything greater or equal to "1.99" means protocol 2 is supported. */ - s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0; - - if (conf_get_int(ssh->conf, CONF_sshprot) == 0) { - if (!s->proto1) { - bombout(("SSH protocol version 1 required by our configuration " - "but not provided by server")); - crStop(0); - } - } else if (conf_get_int(ssh->conf, CONF_sshprot) == 3) { - if (!s->proto2) { - bombout(("SSH protocol version 2 required by our configuration " - "but server only provides (old, insecure) SSH-1")); - crStop(0); - } - } else { - /* No longer support values 1 or 2 for CONF_sshprot */ - assert(!"Unexpected value for CONF_sshprot"); - } - - if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1)) - ssh->version = 2; - else - ssh->version = 1; - - logeventf(ssh, "Using SSH protocol version %d", ssh->version); - - /* Send the version string, if we haven't already */ - if (conf_get_int(ssh->conf, CONF_sshprot) != 3) - ssh_send_verstring(ssh, protoname, s->version); - - if (ssh->version == 2) { - size_t len; - /* - * Record their version string. - */ - len = strcspn(s->vstring, "\015\012"); - ssh->v_s = snewn(len + 1, char); - memcpy(ssh->v_s, s->vstring, len); - ssh->v_s[len] = 0; - - /* - * Initialise SSH-2 protocol. - */ - ssh->protocol = ssh2_protocol; - ssh2_protocol_setup(ssh); - ssh->s_rdpkt = ssh2_rdpkt; - } else { - /* - * Initialise SSH-1 protocol. - */ - ssh->protocol = ssh1_protocol; - ssh1_protocol_setup(ssh); - ssh->s_rdpkt = ssh1_rdpkt; - } - if (ssh->version == 2) - do_ssh2_transport(ssh, NULL, -1, NULL); - - update_specials_menu(ssh->frontend); - ssh->state = SSH_STATE_BEFORE_SIZE; - ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh); - - sfree(s->vstring); - - crFinish(0); -} - -static int do_ssh_connection_init(Ssh ssh, unsigned char c) -{ - /* - * Ordinary SSH begins with the banner "SSH-x.y-...". This is just - * the ssh-connection part, extracted and given a trivial binary - * packet protocol, so we replace 'SSH-' at the start with a new - * name. In proper SSH style (though of course this part of the - * proper SSH protocol _isn't_ subject to this kind of - * DNS-domain-based extension), we define the new name in our - * extension space. - */ - static const char protoname[] = - "SSHCONNECTION@putty.projects.tartarus.org-"; - - struct do_ssh_connection_init_state { - int crLine; - int vslen; - char version[10]; - char *vstring; - int vstrsize; - int i; - }; - crState(do_ssh_connection_init_state); - - crBeginState; - - /* Search for a line beginning with the protocol name prefix in - * the input. */ - for (;;) { - for (s->i = 0; protoname[s->i]; s->i++) { - if ((char)c != protoname[s->i]) goto no; - crReturn(1); - } - break; - no: - while (c != '\012') - crReturn(1); - crReturn(1); - } - - s->vstrsize = sizeof(protoname) + 16; - s->vstring = snewn(s->vstrsize, char); - strcpy(s->vstring, protoname); - s->vslen = strlen(protoname); - s->i = 0; - while (1) { - if (s->vslen >= s->vstrsize - 1) { - s->vstrsize += 16; - s->vstring = sresize(s->vstring, s->vstrsize, char); - } - s->vstring[s->vslen++] = c; - if (s->i >= 0) { - if (c == '-') { - s->version[s->i] = '\0'; - s->i = -1; - } else if (s->i < sizeof(s->version) - 1) - s->version[s->i++] = c; - } else if (c == '\012') - break; - crReturn(1); /* get another char */ - } - - ssh->agentfwd_enabled = FALSE; - ssh->rdpkt2_bare_state.incoming_sequence = 0; - - s->vstring[s->vslen] = 0; - s->vstring[strcspn(s->vstring, "\015\012")] = '\0';/* remove EOL chars */ - logeventf(ssh, "Server version: %s", s->vstring); - ssh_detect_bugs(ssh, s->vstring); - - /* - * Decide which SSH protocol version to support. This is easy in - * bare ssh-connection mode: only 2.0 is legal. - */ - if (ssh_versioncmp(s->version, "2.0") < 0) { - bombout(("Server announces compatibility with SSH-1 in bare ssh-connection protocol")); - crStop(0); - } - if (conf_get_int(ssh->conf, CONF_sshprot) == 0) { - bombout(("Bare ssh-connection protocol cannot be run in SSH-1-only mode")); - crStop(0); - } - - ssh->version = 2; - - logeventf(ssh, "Using bare ssh-connection protocol"); - - /* Send the version string, if we haven't already */ - ssh_send_verstring(ssh, protoname, s->version); - - /* - * Initialise bare connection protocol. - */ - ssh->protocol = ssh2_bare_connection_protocol; - ssh2_bare_connection_protocol_setup(ssh); - ssh->s_rdpkt = ssh2_bare_connection_rdpkt; - - update_specials_menu(ssh->frontend); - ssh->state = SSH_STATE_BEFORE_SIZE; - ssh->pinger = pinger_new(ssh->conf, &ssh_backend, ssh); - - /* - * Get authconn (really just conn) under way. - */ - do_ssh2_authconn(ssh, NULL, 0, NULL); - - sfree(s->vstring); - - crFinish(0); -} - -static void ssh_process_incoming_data(Ssh ssh, - const unsigned char **data, int *datalen) -{ - struct Packet *pktin; - - pktin = ssh->s_rdpkt(ssh, data, datalen); - if (pktin) { - ssh->protocol(ssh, NULL, 0, pktin); - ssh_free_packet(pktin); - } -} - -static void ssh_queue_incoming_data(Ssh ssh, - const unsigned char **data, int *datalen) -{ - bufchain_add(&ssh->queued_incoming_data, *data, *datalen); - *data += *datalen; - *datalen = 0; -} - -static void ssh_process_queued_incoming_data(Ssh ssh) -{ - void *vdata; - const unsigned char *data; - int len, origlen; - - while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) { - bufchain_prefix(&ssh->queued_incoming_data, &vdata, &len); - data = vdata; - origlen = len; - - while (!ssh->frozen && len > 0) - ssh_process_incoming_data(ssh, &data, &len); - - if (origlen > len) - bufchain_consume(&ssh->queued_incoming_data, origlen - len); - } -} - -static void ssh_set_frozen(Ssh ssh, int frozen) -{ - if (ssh->s) - sk_set_frozen(ssh->s, frozen); - ssh->frozen = frozen; -} - -static void ssh_gotdata(Ssh ssh, const unsigned char *data, int datalen) -{ - /* Log raw data, if we're in that mode. */ - if (ssh->logctx) - log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, datalen, - 0, NULL, NULL, 0, NULL); - - crBegin(ssh->ssh_gotdata_crstate); - - /* - * To begin with, feed the characters one by one to the - * protocol initialisation / selection function do_ssh_init(). - * When that returns 0, we're done with the initial greeting - * exchange and can move on to packet discipline. - */ - while (1) { - int ret; /* need not be kept across crReturn */ - if (datalen == 0) - crReturnV; /* more data please */ - ret = ssh->do_ssh_init(ssh, *data); - data++; - datalen--; - if (ret == 0) - break; - } - - /* - * We emerge from that loop when the initial negotiation is - * over and we have selected an s_rdpkt function. Now pass - * everything to s_rdpkt, and then pass the resulting packets - * to the proper protocol handler. - */ - - while (1) { - while (bufchain_size(&ssh->queued_incoming_data) > 0 || datalen > 0) { - if (ssh->frozen) { - ssh_queue_incoming_data(ssh, &data, &datalen); - /* This uses up all data and cannot cause anything interesting - * to happen; indeed, for anything to happen at all, we must - * return, so break out. */ - break; - } else if (bufchain_size(&ssh->queued_incoming_data) > 0) { - /* This uses up some or all data, and may freeze the - * session. */ - ssh_process_queued_incoming_data(ssh); - } else { - /* This uses up some or all data, and may freeze the - * session. */ - ssh_process_incoming_data(ssh, &data, &datalen); - } - /* FIXME this is probably EBW. */ - if (ssh->state == SSH_STATE_CLOSED) - return; - } - /* We're out of data. Go and get some more. */ - crReturnV; - } - crFinishV; -} - -static int ssh_do_close(Ssh ssh, int notify_exit) -{ - int ret = 0; - struct ssh_channel *c; - - ssh->state = SSH_STATE_CLOSED; - expire_timer_context(ssh); - if (ssh->s) { - sk_close(ssh->s); - ssh->s = NULL; - if (notify_exit) - notify_remote_exit(ssh->frontend); - else - ret = 1; - } - /* - * Now we must shut down any port- and X-forwarded channels going - * through this connection. - */ - if (ssh->channels) { - while (NULL != (c = index234(ssh->channels, 0))) { - ssh_channel_close_local(c, NULL); - del234(ssh->channels, c); /* moving next one to index 0 */ - if (ssh->version == 2) - bufchain_clear(&c->v.v2.outbuffer); - sfree(c); - } - } - /* - * Go through port-forwardings, and close any associated - * listening sockets. - */ - if (ssh->portfwds) { - struct ssh_portfwd *pf; - while (NULL != (pf = index234(ssh->portfwds, 0))) { - /* Dispose of any listening socket. */ - if (pf->local) - pfl_terminate(pf->local); - del234(ssh->portfwds, pf); /* moving next one to index 0 */ - free_portfwd(pf); - } - freetree234(ssh->portfwds); - ssh->portfwds = NULL; - } - - /* - * Also stop attempting to connection-share. - */ - if (ssh->connshare) { - sharestate_free(ssh->connshare); - ssh->connshare = NULL; - } - - return ret; -} - -static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port, - const char *error_msg, int error_code) -{ - Ssh ssh = (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->frontend, type, addr, port, - error_msg, error_code, ssh->conf, - ssh->session_started); -} - -void ssh_connshare_log(Ssh ssh, int event, const char *logtext, - const char *ds_err, const char *us_err) -{ - if (event == SHARE_NONE) { - /* In this case, 'logtext' is an error message indicating a - * reason why connection sharing couldn't be set up _at all_. - * Failing that, ds_err and us_err indicate why we couldn't be - * a downstream and an upstream respectively. */ - if (logtext) { - logeventf(ssh, "Could not set up connection sharing: %s", logtext); - } else { - if (ds_err) - logeventf(ssh, "Could not set up connection sharing" - " as downstream: %s", ds_err); - if (us_err) - logeventf(ssh, "Could not set up connection sharing" - " as upstream: %s", us_err); - } - } else if (event == SHARE_DOWNSTREAM) { - /* In this case, 'logtext' is a local endpoint address */ - logeventf(ssh, "Using existing shared connection at %s", logtext); - /* Also we should mention this in the console window to avoid - * confusing users as to why this window doesn't behave the - * usual way. */ - if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { - c_write_str(ssh,"Reusing a shared connection to this server.\r\n"); - } - } else if (event == SHARE_UPSTREAM) { - /* In this case, 'logtext' is a local endpoint address too */ - logeventf(ssh, "Sharing this connection at %s", logtext); - } -} - -static int ssh_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) -{ - Ssh ssh = (Ssh) plug; - int need_notify = ssh_do_close(ssh, FALSE); - - if (!error_msg) { - if (!ssh->close_expected) - error_msg = "Server unexpectedly closed network connection"; - else - error_msg = "Server closed network connection"; - } - - if (ssh->close_expected && ssh->clean_exit && ssh->exitcode < 0) - ssh->exitcode = 0; - - if (need_notify) - notify_remote_exit(ssh->frontend); - - if (error_msg) - logevent(error_msg); - if (!ssh->close_expected || !ssh->clean_exit) - connection_fatal(ssh->frontend, "%s", error_msg); - return 0; -} - -static int ssh_receive(Plug plug, int urgent, char *data, int len) -{ - Ssh ssh = (Ssh) plug; - ssh_gotdata(ssh, (unsigned char *)data, len); - if (ssh->state == SSH_STATE_CLOSED) { - ssh_do_close(ssh, TRUE); - return 0; - } - return 1; -} - -static void ssh_sent(Plug plug, int bufsize) -{ - Ssh ssh = (Ssh) plug; - /* - * If the send backlog on the SSH socket itself clears, we - * should unthrottle the whole world if it was throttled. - */ - if (bufsize < SSH_MAX_BACKLOG) - ssh_throttle_all(ssh, 0, bufsize); -} - -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 int ssh_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *savedhost; - int savedport; - int 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; -} - -/* - * Connect to specified host and port. - * Returns an error message, or NULL on success. - * Also places the canonical host name into `realhost'. It must be - * freed by the caller. - */ -static const char *connect_to_host(Ssh ssh, const char *host, int port, - char **realhost, int nodelay, int keepalive) -{ - static const struct plug_function_table fn_table = { - ssh_socket_log, - ssh_closing, - ssh_receive, - ssh_sent, - NULL - }; - - SockAddr addr; - const char *err; - char *loghost; - int addressfamily, sshprot; - - ssh_hostport_setup(host, port, ssh->conf, - &ssh->savedhost, &ssh->savedport, &loghost); - - ssh->fn = &fn_table; /* make 'ssh' usable as a Plug */ - - /* - * Try connection-sharing, in case that means we don't open a - * socket after all. ssh_connection_sharing_init will connect to a - * previously established upstream if it can, and failing that, - * establish a listening socket for _us_ to be the upstream. In - * the latter case it will return NULL just as if it had done - * nothing, because here we only need to care if we're a - * downstream and need to do our connection setup differently. - */ - ssh->connshare = NULL; - ssh->attempting_connshare = TRUE; /* affects socket logging behaviour */ - ssh->s = ssh_connection_sharing_init(ssh->savedhost, ssh->savedport, - ssh->conf, ssh, &ssh->connshare); - ssh->attempting_connshare = FALSE; - if (ssh->s != NULL) { - /* - * We are a downstream. - */ - ssh->bare_connection = TRUE; - ssh->do_ssh_init = do_ssh_connection_init; - ssh->fullhostname = NULL; - *realhost = dupstr(host); /* best we can do */ - } else { - /* - * We're not a downstream, so open a normal socket. - */ - ssh->do_ssh_init = do_ssh_init; - - /* - * Try to find host. - */ - addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); - addr = name_lookup(host, port, realhost, ssh->conf, addressfamily, - ssh->frontend, "SSH connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return err; - } - ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ - - ssh->s = new_connection(addr, *realhost, port, - 0, 1, nodelay, keepalive, - (Plug) ssh, ssh->conf); - if ((err = sk_socket_error(ssh->s)) != NULL) { - ssh->s = NULL; - notify_remote_exit(ssh->frontend); - return err; - } - } - - /* - * The SSH version number is always fixed (since we no longer support - * fallback between versions), so set it now, and if it's SSH-2, - * send the version string now too. - */ - 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; - ssh_send_verstring(ssh, "SSH-", NULL); - } - - /* - * loghost, if configured, overrides realhost. - */ - if (*loghost) { - sfree(*realhost); - *realhost = dupstr(loghost); - } - - return NULL; -} - -/* - * Throttle or unthrottle the SSH connection. - */ -static void ssh_throttle_conn(Ssh ssh, int adjust) -{ - int old_count = ssh->conn_throttle_count; - ssh->conn_throttle_count += adjust; - assert(ssh->conn_throttle_count >= 0); - if (ssh->conn_throttle_count && !old_count) { - ssh_set_frozen(ssh, 1); - } else if (!ssh->conn_throttle_count && old_count) { - ssh_set_frozen(ssh, 0); - } -} - -static void ssh_agentf_try_forward(struct ssh_channel *c); - -/* - * Throttle or unthrottle _all_ local data streams (for when sends - * on the SSH connection itself back up). - */ -static void ssh_throttle_all(Ssh ssh, int enable, int bufsize) -{ - int i; - struct ssh_channel *c; - - if (enable == ssh->throttled_all) - return; - ssh->throttled_all = enable; - ssh->overall_bufsize = bufsize; - if (!ssh->channels) - return; - for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) { - switch (c->type) { - case CHAN_MAINSESSION: - /* - * This is treated separately, outside the switch. - */ - break; - case CHAN_X11: - x11_override_throttle(c->u.x11.xconn, enable); - break; - case CHAN_AGENT: - /* Agent forwarding channels are buffer-managed by - * checking ssh->throttled_all in ssh_agentf_try_forward. - * So at the moment we _un_throttle again, we must make an - * attempt to do something. */ - if (!enable) - ssh_agentf_try_forward(c); - break; - case CHAN_SOCKDATA: - pfd_override_throttle(c->u.pfd.pf, enable); - break; - } - } -} - -static void ssh_agent_callback(void *sshv, void *reply, int replylen) -{ - Ssh ssh = (Ssh) sshv; - - ssh->auth_agent_query = NULL; - - ssh->agent_response = reply; - ssh->agent_response_len = replylen; - - if (ssh->version == 1) - do_ssh1_login(ssh, NULL, -1, NULL); - else - do_ssh2_authconn(ssh, NULL, -1, NULL); -} - -static void ssh_dialog_callback(void *sshv, int ret) -{ - Ssh ssh = (Ssh) sshv; - - ssh->user_response = ret; - - if (ssh->version == 1) - do_ssh1_login(ssh, NULL, -1, NULL); - else - do_ssh2_transport(ssh, NULL, -1, NULL); - - /* - * This may have unfrozen the SSH connection, so do a - * queued-data run. - */ - ssh_process_queued_incoming_data(ssh); -} - -static void ssh_agentf_got_response(struct ssh_channel *c, - void *reply, int replylen) -{ - c->u.a.pending = NULL; - - assert(!(c->closes & CLOSES_SENT_EOF)); - - 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; - } - - ssh_send_channel_data(c, reply, replylen); -} - -static void ssh_agentf_callback(void *cv, void *reply, int replylen); - -static void ssh_agentf_try_forward(struct ssh_channel *c) -{ - unsigned datalen, lengthfield, messagelen; - unsigned char *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 (c->u.a.pending) - return; - - /* - * If the outgoing side of the channel connection is currently - * throttled (for any reason, either that channel's window size or - * the entire SSH connection being 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 (c->ssh->throttled_all || - (c->ssh->version == 2 && c->v.v2.remwindow == 0)) - return; - - if (c->closes & CLOSES_SENT_EOF) { - /* - * If we've already sent outgoing EOF, there's nothing we can - * do with incoming data except consume it and throw it away. - */ - bufchain_clear(&c->u.a.inbuffer); - return; - } - - while (1) { - /* - * Try to extract a complete message from the input buffer. - */ - datalen = bufchain_size(&c->u.a.inbuffer); - if (datalen < 4) - break; /* not even a length field available yet */ - - bufchain_fetch(&c->u.a.inbuffer, msglen, 4); - lengthfield = GET_32BIT(msglen); - - if (lengthfield > AGENT_MAX_MSGLEN) { - /* - * 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). - */ - ssh_agentf_got_response(c, NULL, 0); - sshfwd_write_eof(c); - return; - } - - if (lengthfield > datalen - 4) - break; /* a whole message is not yet available */ - - messagelen = lengthfield + 4; - - message = snewn(messagelen, unsigned char); - bufchain_fetch(&c->u.a.inbuffer, message, messagelen); - bufchain_consume(&c->u.a.inbuffer, messagelen); - c->u.a.pending = agent_query( - message, messagelen, &reply, &replylen, ssh_agentf_callback, c); - sfree(message); - - if (c->u.a.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. - */ - ssh_agentf_got_response(c, 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 (c->closes & CLOSES_RCVD_EOF) - sshfwd_write_eof(c); -} - -static void ssh_agentf_callback(void *cv, void *reply, int replylen) -{ - struct ssh_channel *c = (struct ssh_channel *)cv; - - ssh_agentf_got_response(c, reply, replylen); - sfree(reply); - - /* - * Now try to extract and send further messages from the channel's - * input-side buffer. - */ - ssh_agentf_try_forward(c); -} - -/* - * Client-initiated disconnection. Send a DISCONNECT if `wire_reason' - * non-NULL, otherwise just close the connection. `client_reason' == NULL - * => log `wire_reason'. - */ -static void ssh_disconnect(Ssh ssh, const char *client_reason, - const char *wire_reason, - int code, int clean_exit) -{ - char *error; - if (!client_reason) - client_reason = wire_reason; - if (client_reason) - error = dupprintf("Disconnected: %s", client_reason); - else - error = dupstr("Disconnected"); - if (wire_reason) { - if (ssh->version == 1) { - send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason, - PKT_END); - } else if (ssh->version == 2) { - struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT); - ssh2_pkt_adduint32(pktout, code); - ssh2_pkt_addstring(pktout, wire_reason); - ssh2_pkt_addstring(pktout, "en"); /* language tag */ - ssh2_pkt_send_noqueue(ssh, pktout); - } - } - ssh->close_expected = TRUE; - ssh->clean_exit = clean_exit; - ssh_closing((Plug)ssh, error, 0, 0); - sfree(error); -} - -int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint, - const struct ssh_signkey *ssh2keytype, - void *ssh2keydata) -{ - if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) { - return -1; /* no manual keys configured */ - } - - if (fingerprint) { - /* - * The 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 colon-separated hex block at the - * end of the string. - */ - const char *p = strrchr(fingerprint, ' '); - fingerprint = p ? p+1 : fingerprint; - /* Quick sanity checks, including making sure it's in lowercase */ - assert(strlen(fingerprint) == 16*3 - 1); - assert(fingerprint[2] == ':'); - assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); - - if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys, - fingerprint)) - return 1; /* success */ - } - - if (ssh2keydata) { - /* - * Construct the base64-encoded public key blob and see if - * that's listed. - */ - unsigned char *binblob; - char *base64blob; - int binlen, atoms, i; - binblob = ssh2keytype->public_blob(ssh2keydata, &binlen); - atoms = (binlen + 2) / 3; - base64blob = snewn(atoms * 4 + 1, char); - for (i = 0; i < atoms; i++) - base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i); - base64blob[atoms * 4] = '\0'; - sfree(binblob); - if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys, - base64blob)) { - sfree(base64blob); - return 1; /* success */ - } - sfree(base64blob); - } - - return 0; -} - -/* - * Handle the key exchange and user authentication phases. - */ -static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen, - struct Packet *pktin) -{ - int i, j, ret; - unsigned char cookie[8], *ptr; - struct MD5Context md5c; - struct do_ssh1_login_state { - int crLine; - int len; - unsigned char *rsabuf; - const unsigned char *keystr1, *keystr2; - unsigned long supported_ciphers_mask, supported_auths_mask; - int tried_publickey, tried_agent; - int tis_auth_refused, ccard_auth_refused; - unsigned char session_id[16]; - int cipher_type; - void *publickey_blob; - int publickey_bloblen; - char *publickey_comment; - int privatekey_available, privatekey_encrypted; - prompts_t *cur_prompt; - char c; - int pwpkt_type; - unsigned char request[5], *response, *p; - int responselen; - int keyi, nkeys; - int authed; - struct RSAKey key; - Bignum challenge; - char *commentp; - int commentlen; - int dlgret; - Filename *keyfile; - struct RSAKey servkey, hostkey; - }; - crState(do_ssh1_login_state); - - crBeginState; - - if (!pktin) - crWaitUntil(pktin); - - if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { - bombout(("Public key packet not received")); - crStop(0); - } - - logevent("Received public keys"); - - ptr = ssh_pkt_getdata(pktin, 8); - if (!ptr) { - bombout(("SSH-1 public key packet stopped before random cookie")); - crStop(0); - } - memcpy(cookie, ptr, 8); - - if (!ssh1_pkt_getrsakey(pktin, &s->servkey, &s->keystr1) || - !ssh1_pkt_getrsakey(pktin, &s->hostkey, &s->keystr2)) { - bombout(("Failed to read SSH-1 public keys from public key packet")); - crStop(0); - } - - /* - * Log the host key fingerprint. - */ - { - char logmsg[80]; - logevent("Host key fingerprint is:"); - strcpy(logmsg, " "); - s->hostkey.comment = NULL; - rsa_fingerprint(logmsg + strlen(logmsg), - sizeof(logmsg) - strlen(logmsg), &s->hostkey); - logevent(logmsg); - } - - ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin); - s->supported_ciphers_mask = ssh_pkt_getuint32(pktin); - s->supported_auths_mask = ssh_pkt_getuint32(pktin); - if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) - s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); - - ssh->v1_local_protoflags = - ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; - ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; - - MD5Init(&md5c); - MD5Update(&md5c, s->keystr2, s->hostkey.bytes); - MD5Update(&md5c, s->keystr1, s->servkey.bytes); - MD5Update(&md5c, cookie, 8); - MD5Final(s->session_id, &md5c); - - for (i = 0; i < 32; i++) - ssh->session_key[i] = random_byte(); - - /* - * Verify that the `bits' and `bytes' parameters match. - */ - if (s->hostkey.bits > s->hostkey.bytes * 8 || - s->servkey.bits > s->servkey.bytes * 8) { - bombout(("SSH-1 public keys were badly formatted")); - crStop(0); - } - - s->len = (s->hostkey.bytes > s->servkey.bytes ? - s->hostkey.bytes : s->servkey.bytes); - - s->rsabuf = snewn(s->len, unsigned char); - - /* - * Verify the host key. - */ - { - /* - * First format the key into a string. - */ - int len = rsastr_len(&s->hostkey); - char fingerprint[100]; - char *keystr = snewn(len, char); - rsastr_fmt(keystr, &s->hostkey); - rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey); - - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL); - if (s->dlgret == 0) { /* did not match */ - bombout(("Host key did not appear in manually configured list")); - sfree(keystr); - crStop(0); - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - ssh_set_frozen(ssh, 1); - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - "rsa", keystr, fingerprint, - ssh_dialog_callback, ssh); - sfree(keystr); -#ifdef FUZZING - s->dlgret = 1; -#endif - if (s->dlgret < 0) { - do { - crReturn(0); - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for user host key response")); - crStop(0); - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at host key verification", - NULL, 0, TRUE); - crStop(0); - } - } else { - sfree(keystr); - } - } - - for (i = 0; i < 32; i++) { - s->rsabuf[i] = ssh->session_key[i]; - if (i < 16) - s->rsabuf[i] ^= s->session_id[i]; - } - - if (s->hostkey.bytes > s->servkey.bytes) { - ret = rsaencrypt(s->rsabuf, 32, &s->servkey); - if (ret) - ret = rsaencrypt(s->rsabuf, s->servkey.bytes, &s->hostkey); - } else { - ret = rsaencrypt(s->rsabuf, 32, &s->hostkey); - if (ret) - ret = rsaencrypt(s->rsabuf, s->hostkey.bytes, &s->servkey); - } - if (!ret) { - bombout(("SSH-1 public key encryptions failed due to bad formatting")); - crStop(0); - } - - logevent("Encrypted session key"); - - { - int cipher_chosen = 0, warn = 0; - const char *cipher_string = NULL; - int i; - for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { - int next_cipher = conf_get_int_int(ssh->conf, - CONF_ssh_cipherlist, i); - if (next_cipher == CIPHER_WARN) { - /* If/when we choose a cipher, warn about it */ - warn = 1; - } else if (next_cipher == CIPHER_AES) { - /* XXX Probably don't need to mention this. */ - logevent("AES not supported in SSH-1, skipping"); - } else { - switch (next_cipher) { - case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES; - cipher_string = "3DES"; break; - case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH; - cipher_string = "Blowfish"; break; - case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES; - cipher_string = "single-DES"; break; - } - if (s->supported_ciphers_mask & (1 << s->cipher_type)) - cipher_chosen = 1; - } - } - if (!cipher_chosen) { - if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) - bombout(("Server violates SSH-1 protocol by not " - "supporting 3DES encryption")); - else - /* shouldn't happen */ - bombout(("No supported ciphers found")); - crStop(0); - } - - /* Warn about chosen cipher if necessary. */ - if (warn) { - ssh_set_frozen(ssh, 1); - s->dlgret = askalg(ssh->frontend, "cipher", cipher_string, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - do { - crReturn(0); - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for user response")); - crStop(0); - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at cipher warning", NULL, - 0, TRUE); - crStop(0); - } - } - } - - switch (s->cipher_type) { - case SSH_CIPHER_3DES: - logevent("Using 3DES encryption"); - break; - case SSH_CIPHER_DES: - logevent("Using single-DES encryption"); - break; - case SSH_CIPHER_BLOWFISH: - logevent("Using Blowfish encryption"); - break; - } - - send_packet(ssh, SSH1_CMSG_SESSION_KEY, - PKT_CHAR, s->cipher_type, - PKT_DATA, cookie, 8, - PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF, - PKT_DATA, s->rsabuf, s->len, - PKT_INT, ssh->v1_local_protoflags, PKT_END); - - logevent("Trying to enable encryption..."); - - sfree(s->rsabuf); - - ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH_CIPHER_DES ? &ssh_des : - &ssh_3des); - ssh->v1_cipher_ctx = ssh->cipher->make_context(); - ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key); - logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name); - - ssh->crcda_ctx = crcda_make_context(); - logevent("Installing CRC compensation attack detector"); - - if (s->servkey.modulus) { - sfree(s->servkey.modulus); - s->servkey.modulus = NULL; - } - if (s->servkey.exponent) { - sfree(s->servkey.exponent); - s->servkey.exponent = NULL; - } - if (s->hostkey.modulus) { - sfree(s->hostkey.modulus); - s->hostkey.modulus = NULL; - } - if (s->hostkey.exponent) { - sfree(s->hostkey.exponent); - s->hostkey.exponent = NULL; - } - crWaitUntil(pktin); - - if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Encryption not successfully enabled")); - crStop(0); - } - - logevent("Successfully started encryption"); - - fflush(stdout); /* FIXME eh? */ - { - if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { - int ret; /* need not be kept over crReturn */ - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntil(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* - * Failed to get a username. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStop(0); - } - ssh->username = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } - - send_packet(ssh, SSH1_CMSG_USER, PKT_STR, ssh->username, PKT_END); - { - char *userlog = dupprintf("Sent username \"%s\"", ssh->username); - logevent(userlog); - if (flags & FLAG_INTERACTIVE && - (!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) { - c_write_str(ssh, userlog); - c_write_str(ssh, "\r\n"); - } - sfree(userlog); - } - } - - crWaitUntil(pktin); - - if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) { - /* We must not attempt PK auth. Pretend we've already tried it. */ - s->tried_publickey = s->tried_agent = 1; - } else { - s->tried_publickey = s->tried_agent = 0; - } - s->tis_auth_refused = s->ccard_auth_refused = 0; - /* - * Load the public half of any configured keyfile for later use. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - logeventf(ssh, "Reading key file \"%.150s\"", - filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - if (rsakey_pubblob(s->keyfile, - &s->publickey_blob, &s->publickey_bloblen, - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); - if (!s->privatekey_available) - logeventf(ssh, "Key file contains public key only"); - s->privatekey_encrypted = rsakey_encrypted(s->keyfile, - NULL); - } else { - char *msgbuf; - logeventf(ssh, "Unable to load key (%s)", error); - msgbuf = dupprintf("Unable to load key file " - "\"%.150s\" (%s)\r\n", - filename_to_str(s->keyfile), - error); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } else { - char *msgbuf; - logeventf(ssh, "Unable to use this key file (%s)", - key_type_to_str(keytype)); - msgbuf = dupprintf("Unable to use key file \"%.150s\"" - " (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } else - s->publickey_blob = NULL; - - while (pktin->type == SSH1_SMSG_FAILURE) { - s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - - if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists() && !s->tried_agent) { - /* - * Attempt RSA authentication using Pageant. - */ - void *r; - - s->authed = FALSE; - s->tried_agent = 1; - logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - PUT_32BIT(s->request, 1); - s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES; - ssh->auth_agent_query = agent_query( - s->request, 5, &r, &s->responselen, ssh_agent_callback, ssh); - if (ssh->auth_agent_query) { - do { - crReturn(0); - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for agent response")); - crStop(0); - } - } while (pktin || inlen > 0); - r = ssh->agent_response; - s->responselen = ssh->agent_response_len; - } - s->response = (unsigned char *) r; - if (s->response && s->responselen >= 5 && - s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - s->p = s->response + 5; - s->nkeys = toint(GET_32BIT(s->p)); - if (s->nkeys < 0) { - logeventf(ssh, "Pageant reported negative key count %d", - s->nkeys); - s->nkeys = 0; - } - s->p += 4; - logeventf(ssh, "Pageant has %d SSH-1 keys", s->nkeys); - for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - unsigned char *pkblob = s->p; - s->p += 4; - { - int n, ok = FALSE; - do { /* do while (0) to make breaking easy */ - n = ssh1_read_bignum - (s->p, toint(s->responselen-(s->p-s->response)), - &s->key.exponent); - if (n < 0) - break; - s->p += n; - n = ssh1_read_bignum - (s->p, toint(s->responselen-(s->p-s->response)), - &s->key.modulus); - if (n < 0) - break; - s->p += n; - if (s->responselen - (s->p-s->response) < 4) - break; - s->commentlen = toint(GET_32BIT(s->p)); - s->p += 4; - if (s->commentlen < 0 || - toint(s->responselen - (s->p-s->response)) < - s->commentlen) - break; - s->commentp = (char *)s->p; - s->p += s->commentlen; - ok = TRUE; - } while (0); - if (!ok) { - logevent("Pageant key list packet was truncated"); - break; - } - } - if (s->publickey_blob) { - if (!memcmp(pkblob, s->publickey_blob, - s->publickey_bloblen)) { - logeventf(ssh, "Pageant key #%d matches " - "configured key file", s->keyi); - s->tried_publickey = 1; - } else - /* Skip non-configured key */ - continue; - } - logeventf(ssh, "Trying Pageant key #%d", s->keyi); - send_packet(ssh, SSH1_CMSG_AUTH_RSA, - PKT_BIGNUM, s->key.modulus, PKT_END); - crWaitUntil(pktin); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - logevent("Key refused"); - continue; - } - logevent("Received RSA challenge"); - if ((s->challenge = ssh1_pkt_getmp(pktin)) == NULL) { - bombout(("Server's RSA challenge was badly formatted")); - crStop(0); - } - - { - char *agentreq, *q, *ret; - void *vret; - int len, retlen; - len = 1 + 4; /* message type, bit count */ - len += ssh1_bignum_length(s->key.exponent); - len += ssh1_bignum_length(s->key.modulus); - len += ssh1_bignum_length(s->challenge); - len += 16; /* session id */ - len += 4; /* response format */ - agentreq = snewn(4 + len, char); - PUT_32BIT(agentreq, len); - q = agentreq + 4; - *q++ = SSH1_AGENTC_RSA_CHALLENGE; - PUT_32BIT(q, bignum_bitcount(s->key.modulus)); - q += 4; - q += ssh1_write_bignum(q, s->key.exponent); - q += ssh1_write_bignum(q, s->key.modulus); - q += ssh1_write_bignum(q, s->challenge); - memcpy(q, s->session_id, 16); - q += 16; - PUT_32BIT(q, 1); /* response format */ - ssh->auth_agent_query = agent_query( - agentreq, len + 4, &vret, &retlen, - ssh_agent_callback, ssh); - if (ssh->auth_agent_query) { - sfree(agentreq); - do { - crReturn(0); - if (pktin) { - bombout(("Unexpected data from server" - " while waiting for agent" - " response")); - crStop(0); - } - } while (pktin || inlen > 0); - vret = ssh->agent_response; - retlen = ssh->agent_response_len; - } else - sfree(agentreq); - ret = vret; - if (ret) { - if (ret[4] == SSH1_AGENT_RSA_RESPONSE) { - logevent("Sending Pageant's response"); - send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, - PKT_DATA, ret + 5, 16, - PKT_END); - sfree(ret); - crWaitUntil(pktin); - if (pktin->type == SSH1_SMSG_SUCCESS) { - logevent - ("Pageant's response accepted"); - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticated using" - " RSA key \""); - c_write(ssh, s->commentp, - s->commentlen); - c_write_str(ssh, "\" from agent\r\n"); - } - s->authed = TRUE; - } else - logevent - ("Pageant's response not accepted"); - } else { - logevent - ("Pageant failed to answer challenge"); - sfree(ret); - } - } else { - logevent("No reply received from Pageant"); - } - } - freebn(s->key.exponent); - freebn(s->key.modulus); - freebn(s->challenge); - if (s->authed) - break; - } - sfree(s->response); - if (s->publickey_blob && !s->tried_publickey) - logevent("Configured key file not in Pageant"); - } else { - logevent("Failed to get reply from Pageant"); - } - if (s->authed) - break; - } - if (s->publickey_blob && s->privatekey_available && - !s->tried_publickey) { - /* - * Try public key authentication with the specified - * key file. - */ - int got_passphrase; /* need not be kept over crReturn */ - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Trying public key authentication.\r\n"); - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - logeventf(ssh, "Trying public key \"%s\"", - filename_to_str(s->keyfile)); - s->tried_publickey = 1; - got_passphrase = FALSE; - while (!got_passphrase) { - /* - * Get a passphrase, if necessary. - */ - char *passphrase = NULL; /* only written after crReturn */ - const char *error; - if (!s->privatekey_encrypted) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "No passphrase required.\r\n"); - passphrase = NULL; - } else { - int ret; /* need not be kept over crReturn */ - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = FALSE; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%.100s\": ", - s->publickey_comment), FALSE); - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntil(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* Failed to get a passphrase. Terminate. */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - 0, TRUE); - crStop(0); - } - passphrase = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } - /* - * Try decrypting key with passphrase. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - ret = loadrsakey(s->keyfile, &s->key, passphrase, - &error); - if (passphrase) { - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (ret == 1) { - /* Correct passphrase. */ - got_passphrase = TRUE; - } else if (ret == 0) { - c_write_str(ssh, "Couldn't load private key from "); - c_write_str(ssh, filename_to_str(s->keyfile)); - c_write_str(ssh, " ("); - c_write_str(ssh, error); - c_write_str(ssh, ").\r\n"); - got_passphrase = FALSE; - break; /* go and try something else */ - } else if (ret == -1) { - c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ - got_passphrase = FALSE; - /* and try again */ - } else { - assert(0 && "unexpected return from loadrsakey()"); - got_passphrase = FALSE; /* placate optimisers */ - } - } - - if (got_passphrase) { - - /* - * Send a public key attempt. - */ - send_packet(ssh, SSH1_CMSG_AUTH_RSA, - PKT_BIGNUM, s->key.modulus, PKT_END); - - crWaitUntil(pktin); - if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused our public key.\r\n"); - continue; /* go and try something else */ - } - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - bombout(("Bizarre response to offer of public key")); - crStop(0); - } - - { - int i; - unsigned char buffer[32]; - Bignum challenge, response; - - if ((challenge = ssh1_pkt_getmp(pktin)) == NULL) { - bombout(("Server's RSA challenge was badly formatted")); - crStop(0); - } - response = rsadecrypt(challenge, &s->key); - freebn(s->key.private_exponent);/* burn the evidence */ - - for (i = 0; i < 32; i++) { - buffer[i] = bignum_byte(response, 31 - i); - } - - MD5Init(&md5c); - MD5Update(&md5c, buffer, 32); - MD5Update(&md5c, s->session_id, 16); - MD5Final(buffer, &md5c); - - send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE, - PKT_DATA, buffer, 16, PKT_END); - - freebn(challenge); - freebn(response); - } - - crWaitUntil(pktin); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Failed to authenticate with" - " our public key.\r\n"); - continue; /* go and try something else */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Bizarre response to RSA authentication response")); - crStop(0); - } - - break; /* we're through! */ - } - - } - - /* - * Otherwise, try various forms of password-like authentication. - */ - s->cur_prompt = new_prompts(ssh->frontend); - - if (conf_get_int(ssh->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && - !s->tis_auth_refused) { - s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; - logevent("Requested TIS authentication"); - send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END); - crWaitUntil(pktin); - if (pktin->type != SSH1_SMSG_AUTH_TIS_CHALLENGE) { - logevent("TIS authentication declined"); - if (flags & FLAG_INTERACTIVE) - c_write_str(ssh, "TIS authentication refused.\r\n"); - s->tis_auth_refused = 1; - continue; - } else { - char *challenge; - int challengelen; - char *instr_suf, *prompt; - - ssh_pkt_getstring(pktin, &challenge, &challengelen); - if (!challenge) { - bombout(("TIS challenge packet was badly formed")); - crStop(0); - } - logevent("Received TIS challenge"); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH TIS authentication"); - /* Prompt heuristic comes from OpenSSH */ - if (memchr(challenge, '\n', challengelen)) { - instr_suf = dupstr(""); - prompt = dupprintf("%.*s", challengelen, challenge); - } else { - instr_suf = dupprintf("%.*s", challengelen, challenge); - prompt = dupstr("Response: "); - } - s->cur_prompt->instruction = - dupprintf("Using TIS authentication.%s%s", - (*instr_suf) ? "\n" : "", - instr_suf); - s->cur_prompt->instr_reqd = TRUE; - add_prompt(s->cur_prompt, prompt, FALSE); - sfree(instr_suf); - } - } - if (conf_get_int(ssh->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { - s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; - logevent("Requested CryptoCard authentication"); - send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END); - crWaitUntil(pktin); - if (pktin->type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - logevent("CryptoCard authentication declined"); - c_write_str(ssh, "CryptoCard authentication refused.\r\n"); - s->ccard_auth_refused = 1; - continue; - } else { - char *challenge; - int challengelen; - char *instr_suf, *prompt; - - ssh_pkt_getstring(pktin, &challenge, &challengelen); - if (!challenge) { - bombout(("CryptoCard challenge packet was badly formed")); - crStop(0); - } - logevent("Received CryptoCard challenge"); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); - s->cur_prompt->name_reqd = FALSE; - /* Prompt heuristic comes from OpenSSH */ - if (memchr(challenge, '\n', challengelen)) { - instr_suf = dupstr(""); - prompt = dupprintf("%.*s", challengelen, challenge); - } else { - instr_suf = dupprintf("%.*s", challengelen, challenge); - prompt = dupstr("Response: "); - } - s->cur_prompt->instruction = - dupprintf("Using CryptoCard authentication.%s%s", - (*instr_suf) ? "\n" : "", - instr_suf); - s->cur_prompt->instr_reqd = TRUE; - add_prompt(s->cur_prompt, prompt, FALSE); - sfree(instr_suf); - } - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { - bombout(("No supported authentication methods available")); - crStop(0); - } - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - ssh->username, ssh->savedhost), - FALSE); - } - - /* - * Show password prompt, having first obtained it via a TIS - * or CryptoCard exchange if we're doing TIS or CryptoCard - * authentication. - */ - { - int ret; /* need not be kept over crReturn */ - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntil(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* - * Failed to get a password (for example - * because one was supplied on the command line - * which has already failed to work). Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, TRUE); - crStop(0); - } - } - - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - /* - * Defence against traffic analysis: we send a - * whole bunch of packets containing strings of - * different lengths. One of these strings is the - * password, in a SSH1_CMSG_AUTH_PASSWORD packet. - * The others are all random data in - * SSH1_MSG_IGNORE packets. This way a passive - * listener can't tell which is the password, and - * hence can't deduce the password length. - * - * Anybody with a password length greater than 16 - * bytes is going to have enough entropy in their - * password that a listener won't find it _that_ - * much help to know how long it is. So what we'll - * do is: - * - * - if password length < 16, we send 15 packets - * containing string lengths 1 through 15 - * - * - otherwise, we let N be the nearest multiple - * of 8 below the password length, and send 8 - * packets containing string lengths N through - * N+7. This won't obscure the order of - * magnitude of the password length, but it will - * introduce a bit of extra uncertainty. - * - * A few servers can't deal with SSH1_MSG_IGNORE, at - * least in this context. For these servers, we need - * an alternative defence. We make use of the fact - * that the password is interpreted as a C string: - * so we can append a NUL, then some random data. - * - * A few servers can deal with neither SSH1_MSG_IGNORE - * here _nor_ a padded password string. - * For these servers we are left with no defences - * against password length sniffing. - */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && - !(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can deal with SSH1_MSG_IGNORE, so - * we can use the primary defence. - */ - int bottom, top, pwlen, i; - char *randomstr; - - pwlen = strlen(s->cur_prompt->prompts[0]->result); - if (pwlen < 16) { - bottom = 0; /* zero length passwords are OK! :-) */ - top = 15; - } else { - bottom = pwlen & ~7; - top = bottom + 7; - } - - assert(pwlen >= bottom && pwlen <= top); - - randomstr = snewn(top + 1, char); - - for (i = bottom; i <= top; i++) { - if (i == pwlen) { - defer_packet(ssh, s->pwpkt_type, - PKT_STR,s->cur_prompt->prompts[0]->result, - PKT_END); - } else { - for (j = 0; j < i; j++) { - do { - randomstr[j] = random_byte(); - } while (randomstr[j] == '\0'); - } - randomstr[i] = '\0'; - defer_packet(ssh, SSH1_MSG_IGNORE, - PKT_STR, randomstr, PKT_END); - } - } - logevent("Sending password with camouflage packets"); - ssh_pkt_defersend(ssh); - sfree(randomstr); - } - else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) { - /* - * The server can't deal with SSH1_MSG_IGNORE - * but can deal with padded passwords, so we - * can use the secondary defence. - */ - char string[64]; - char *ss; - int len; - - len = strlen(s->cur_prompt->prompts[0]->result); - if (len < sizeof(string)) { - ss = string; - strcpy(string, s->cur_prompt->prompts[0]->result); - len++; /* cover the zero byte */ - while (len < sizeof(string)) { - string[len++] = (char) random_byte(); - } - } else { - ss = s->cur_prompt->prompts[0]->result; - } - logevent("Sending length-padded password"); - send_packet(ssh, s->pwpkt_type, - PKT_INT, len, PKT_DATA, ss, len, - PKT_END); - } else { - /* - * The server is believed unable to cope with - * any of our password camouflage methods. - */ - int len; - len = strlen(s->cur_prompt->prompts[0]->result); - logevent("Sending unpadded password"); - send_packet(ssh, s->pwpkt_type, - PKT_INT, len, - PKT_DATA, s->cur_prompt->prompts[0]->result, len, - PKT_END); - } - } else { - send_packet(ssh, s->pwpkt_type, - PKT_STR, s->cur_prompt->prompts[0]->result, - PKT_END); - } - logevent("Sent password"); - free_prompts(s->cur_prompt); - crWaitUntil(pktin); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (flags & FLAG_VERBOSE) - c_write_str(ssh, "Access denied\r\n"); - logevent("Authentication refused"); - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - bombout(("Strange packet received, type %d", pktin->type)); - crStop(0); - } - } - - /* Clear up */ - if (s->publickey_blob) { - sfree(s->publickey_blob); - sfree(s->publickey_comment); - } - - logevent("Authentication successful"); - - crFinish(1); -} - -static void ssh_channel_try_eof(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - assert(c->pending_eof); /* precondition for calling us */ - if (c->halfopen) - return; /* can't close: not even opened yet */ - if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0) - return; /* can't send EOF: pending outgoing data */ - - c->pending_eof = FALSE; /* we're about to send it */ - if (ssh->version == 1) { - send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid, - PKT_END); - c->closes |= CLOSES_SENT_EOF; - } else { - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes |= CLOSES_SENT_EOF; - ssh2_channel_check_close(c); - } -} - -Conf *sshfwd_get_conf(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - return ssh->conf; -} - -void sshfwd_write_eof(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - - if (ssh->state == SSH_STATE_CLOSED) - return; - - if (c->closes & CLOSES_SENT_EOF) - return; - - c->pending_eof = TRUE; - ssh_channel_try_eof(c); -} - -void sshfwd_unclean_close(struct ssh_channel *c, const char *err) -{ - Ssh ssh = c->ssh; - char *reason; - - if (ssh->state == SSH_STATE_CLOSED) - return; - - reason = dupprintf("due to local error: %s", err); - ssh_channel_close_local(c, reason); - sfree(reason); - c->pending_eof = FALSE; /* this will confuse a zombie channel */ - - ssh2_channel_check_close(c); -} - -int sshfwd_write(struct ssh_channel *c, char *buf, int len) -{ - Ssh ssh = c->ssh; - - if (ssh->state == SSH_STATE_CLOSED) - return 0; - - return ssh_send_channel_data(c, buf, len); -} - -void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) -{ - Ssh ssh = c->ssh; - - if (ssh->state == SSH_STATE_CLOSED) - return; - - ssh_channel_unthrottle(c, bufsize); -} - -static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin) -{ - struct queued_handler *qh = ssh->qhead; - - assert(qh != NULL); - - assert(pktin->type == qh->msg1 || pktin->type == qh->msg2); - - if (qh->msg1 > 0) { - assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler); - ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1; - } - if (qh->msg2 > 0) { - assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler); - ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2; - } - - if (qh->next) { - ssh->qhead = qh->next; - - if (ssh->qhead->msg1 > 0) { - ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; - ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler; - } - if (ssh->qhead->msg2 > 0) { - ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; - ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler; - } - } else { - ssh->qhead = ssh->qtail = NULL; - } - - qh->handler(ssh, pktin, qh->ctx); - - sfree(qh); -} - -static void ssh_queue_handler(Ssh ssh, int msg1, int msg2, - chandler_fn_t handler, void *ctx) -{ - struct queued_handler *qh; - - qh = snew(struct queued_handler); - qh->msg1 = msg1; - qh->msg2 = msg2; - qh->handler = handler; - qh->ctx = ctx; - qh->next = NULL; - - if (ssh->qtail == NULL) { - ssh->qhead = qh; - - if (qh->msg1 > 0) { - ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1]; - ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler; - } - if (qh->msg2 > 0) { - ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2]; - ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler; - } - } else { - ssh->qtail->next = qh; - } - ssh->qtail = qh; -} - -static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx) -{ - struct ssh_rportfwd *rpf, *pf = (struct ssh_rportfwd *)ctx; - - if (pktin->type == (ssh->version == 1 ? SSH1_SMSG_SUCCESS : - SSH2_MSG_REQUEST_SUCCESS)) { - logeventf(ssh, "Remote port forwarding from %s enabled", - pf->sportdesc); - } else { - logeventf(ssh, "Remote port forwarding from %s refused", - pf->sportdesc); - - rpf = del234(ssh->rportfwds, pf); - assert(rpf == pf); - pf->pfrec->remote = NULL; - free_rportfwd(pf); - } -} - -int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, - void *share_ctx) -{ - struct ssh_rportfwd *pf = snew(struct ssh_rportfwd); - pf->dhost = NULL; - pf->dport = 0; - pf->share_ctx = share_ctx; - pf->shost = dupstr(shost); - pf->sport = sport; - pf->sportdesc = NULL; - if (!ssh->rportfwds) { - assert(ssh->version == 2); - ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); - } - if (add234(ssh->rportfwds, pf) != pf) { - sfree(pf->shost); - sfree(pf); - return FALSE; - } - return TRUE; -} - -static void ssh_sharing_global_request_response(Ssh ssh, struct Packet *pktin, - void *ctx) -{ - share_got_pkt_from_server(ctx, pktin->type, - pktin->body, pktin->length); -} - -void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx) -{ - ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, SSH2_MSG_REQUEST_FAILURE, - ssh_sharing_global_request_response, share_ctx); -} - -static void ssh_setup_portfwd(Ssh ssh, Conf *conf) -{ - struct ssh_portfwd *epf; - int i; - char *key, *val; - - if (!ssh->portfwds) { - ssh->portfwds = newtree234(ssh_portcmp); - } else { - /* - * Go through the existing port forwardings and tag them - * with status==DESTROY. Any that we want to keep will be - * re-enabled (status==KEEP) as we go through the - * configuration and find out which bits are the same as - * they were before. - */ - struct ssh_portfwd *epf; - int i; - for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) - epf->status = DESTROY; - } - - 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(ssh, "Service lookup failed for source" - " port \"%s\"", sports); - } - } - - if (type == 'L' && !strcmp(val, "D")) { - /* dynamic forwarding */ - host = NULL; - dports = NULL; - dport = -1; - dserv = 0; - type = 'D'; - } else { - /* ordinary forwarding */ - vp = val; - vp2 = vp + 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(ssh, "Service lookup failed for destination" - " port \"%s\"", dports); - } - } - } - - if (sport && dport) { - /* Set up a description of the source port. */ - struct ssh_portfwd *pfrec, *epfrec; - - pfrec = snew(struct ssh_portfwd); - pfrec->type = type; - pfrec->saddr = saddr; - pfrec->sserv = sserv ? dupstr(sports) : NULL; - pfrec->sport = sport; - pfrec->daddr = host; - pfrec->dserv = dserv ? dupstr(dports) : NULL; - pfrec->dport = dport; - pfrec->local = NULL; - pfrec->remote = NULL; - pfrec->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : - address_family == '6' ? ADDRTYPE_IPV6 : - ADDRTYPE_UNSPEC); - - epfrec = add234(ssh->portfwds, pfrec); - if (epfrec != pfrec) { - if (epfrec->status == DESTROY) { - /* - * We already have a port forwarding up and running - * with precisely these parameters. Hence, no need - * to do anything; simply re-tag the existing one - * as KEEP. - */ - epfrec->status = KEEP; - } - /* - * Anything else indicates that there was a duplicate - * in our input, which we'll silently ignore. - */ - free_portfwd(pfrec); - } else { - pfrec->status = CREATE; - } - } else { - sfree(saddr); - sfree(host); - } - } - - /* - * Now go through and destroy any port forwardings which were - * not re-enabled. - */ - for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) - if (epf->status == DESTROY) { - char *message; - - message = dupprintf("%s port forwarding from %s%s%d", - epf->type == 'L' ? "local" : - epf->type == 'R' ? "remote" : "dynamic", - epf->saddr ? epf->saddr : "", - epf->saddr ? ":" : "", - epf->sport); - - if (epf->type != 'D') { - char *msg2 = dupprintf("%s to %s:%d", message, - epf->daddr, epf->dport); - sfree(message); - message = msg2; - } - - logeventf(ssh, "Cancelling %s", message); - sfree(message); - - /* epf->remote or epf->local may be NULL if setting up a - * forwarding failed. */ - if (epf->remote) { - struct ssh_rportfwd *rpf = epf->remote; - struct Packet *pktout; - - /* - * Cancel the port forwarding at the server - * end. - */ - if (ssh->version == 1) { - /* - * We cannot cancel listening ports on the - * server side in SSH-1! There's no message - * to support it. Instead, we simply remove - * the rportfwd record from the local end - * so that any connections the server tries - * to make on it are rejected. - */ - } else { - pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); - ssh2_pkt_addstring(pktout, "cancel-tcpip-forward"); - ssh2_pkt_addbool(pktout, 0);/* _don't_ want reply */ - if (epf->saddr) { - ssh2_pkt_addstring(pktout, epf->saddr); - } else if (conf_get_int(conf, CONF_rport_acceptall)) { - /* XXX: rport_acceptall may not represent - * what was used to open the original connection, - * since it's reconfigurable. */ - ssh2_pkt_addstring(pktout, ""); - } else { - ssh2_pkt_addstring(pktout, "localhost"); - } - ssh2_pkt_adduint32(pktout, epf->sport); - ssh2_pkt_send(ssh, pktout); - } - - del234(ssh->rportfwds, rpf); - free_rportfwd(rpf); - } else if (epf->local) { - pfl_terminate(epf->local); - } - - delpos234(ssh->portfwds, i); - free_portfwd(epf); - i--; /* so we don't skip one in the list */ - } - - /* - * And finally, set up any new port forwardings (status==CREATE). - */ - for (i = 0; (epf = index234(ssh->portfwds, i)) != NULL; i++) - if (epf->status == CREATE) { - char *sportdesc, *dportdesc; - sportdesc = dupprintf("%s%s%s%s%d%s", - epf->saddr ? epf->saddr : "", - epf->saddr ? ":" : "", - epf->sserv ? epf->sserv : "", - epf->sserv ? "(" : "", - epf->sport, - epf->sserv ? ")" : ""); - if (epf->type == 'D') { - dportdesc = NULL; - } else { - dportdesc = dupprintf("%s:%s%s%d%s", - epf->daddr, - epf->dserv ? epf->dserv : "", - epf->dserv ? "(" : "", - epf->dport, - epf->dserv ? ")" : ""); - } - - if (epf->type == 'L') { - char *err = pfl_listen(epf->daddr, epf->dport, - epf->saddr, epf->sport, - ssh, conf, &epf->local, - epf->addressfamily); - - logeventf(ssh, "Local %sport %s forwarding to %s%s%s", - epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, dportdesc, - err ? " failed: " : "", err ? err : ""); - if (err) - sfree(err); - } else if (epf->type == 'D') { - char *err = pfl_listen(NULL, -1, epf->saddr, epf->sport, - ssh, conf, &epf->local, - epf->addressfamily); - - logeventf(ssh, "Local %sport %s SOCKS dynamic forwarding%s%s", - epf->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - epf->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, - err ? " failed: " : "", err ? err : ""); - - if (err) - sfree(err); - } else { - struct ssh_rportfwd *pf; - - /* - * Ensure the remote port forwardings tree exists. - */ - if (!ssh->rportfwds) { - if (ssh->version == 1) - ssh->rportfwds = newtree234(ssh_rportcmp_ssh1); - else - ssh->rportfwds = newtree234(ssh_rportcmp_ssh2); - } - - pf = snew(struct ssh_rportfwd); - pf->share_ctx = NULL; - pf->dhost = dupstr(epf->daddr); - pf->dport = epf->dport; - if (epf->saddr) { - pf->shost = dupstr(epf->saddr); - } else if (conf_get_int(conf, CONF_rport_acceptall)) { - pf->shost = dupstr(""); - } else { - pf->shost = dupstr("localhost"); - } - pf->sport = epf->sport; - if (add234(ssh->rportfwds, pf) != pf) { - logeventf(ssh, "Duplicate remote port forwarding to %s:%d", - epf->daddr, epf->dport); - sfree(pf); - } else { - logeventf(ssh, "Requesting remote port %s" - " forward to %s", sportdesc, dportdesc); - - pf->sportdesc = sportdesc; - sportdesc = NULL; - epf->remote = pf; - pf->pfrec = epf; - - if (ssh->version == 1) { - send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST, - PKT_INT, epf->sport, - PKT_STR, epf->daddr, - PKT_INT, epf->dport, - PKT_END); - ssh_queue_handler(ssh, SSH1_SMSG_SUCCESS, - SSH1_SMSG_FAILURE, - ssh_rportfwd_succfail, pf); - } else { - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_GLOBAL_REQUEST); - ssh2_pkt_addstring(pktout, "tcpip-forward"); - ssh2_pkt_addbool(pktout, 1);/* want reply */ - ssh2_pkt_addstring(pktout, pf->shost); - ssh2_pkt_adduint32(pktout, pf->sport); - ssh2_pkt_send(ssh, pktout); - - ssh_queue_handler(ssh, SSH2_MSG_REQUEST_SUCCESS, - SSH2_MSG_REQUEST_FAILURE, - ssh_rportfwd_succfail, pf); - } - } - } - sfree(sportdesc); - sfree(dportdesc); - } -} - -static void ssh1_smsg_stdout_stderr_data(Ssh ssh, struct Packet *pktin) -{ - char *string; - int stringlen, bufsize; - - ssh_pkt_getstring(pktin, &string, &stringlen); - if (string == NULL) { - bombout(("Incoming terminal data packet was badly formed")); - return; - } - - bufsize = from_backend(ssh->frontend, pktin->type == SSH1_SMSG_STDERR_DATA, - string, stringlen); - if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { - ssh->v1_stdout_throttling = 1; - ssh_throttle_conn(ssh, +1); - } -} - -static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin) -{ - /* Remote side is trying to open a channel to talk to our - * X-Server. Give them back a local channel number. */ - struct ssh_channel *c; - int remoteid = ssh_pkt_getuint32(pktin); - - logevent("Received X11 connect request"); - /* Refuse if X11 forwarding is disabled. */ - if (!ssh->X11_fwd_enabled) { - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, - PKT_INT, remoteid, PKT_END); - logevent("Rejected X11 connect request"); - } else { - c = snew(struct ssh_channel); - c->ssh = ssh; - - ssh_channel_init(c); - c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1); - c->remoteid = remoteid; - c->halfopen = FALSE; - c->type = CHAN_X11; /* identify channel type */ - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, - PKT_INT, c->remoteid, PKT_INT, - c->localid, PKT_END); - logevent("Opened X11 forward channel"); - } -} - -static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin) -{ - /* Remote side is trying to open a channel to talk to our - * agent. Give them back a local channel number. */ - struct ssh_channel *c; - int remoteid = ssh_pkt_getuint32(pktin); - - /* Refuse if agent forwarding is disabled. */ - if (!ssh->agentfwd_enabled) { - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, - PKT_INT, remoteid, PKT_END); - } else { - c = snew(struct ssh_channel); - c->ssh = ssh; - ssh_channel_init(c); - c->remoteid = remoteid; - c->halfopen = FALSE; - c->type = CHAN_AGENT; /* identify channel type */ - c->u.a.pending = NULL; - bufchain_init(&c->u.a.inbuffer); - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, - PKT_INT, c->remoteid, PKT_INT, c->localid, - PKT_END); - } -} - -static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) -{ - /* Remote side is trying to open a channel to talk to a - * forwarded port. Give them back a local channel number. */ - struct ssh_rportfwd pf, *pfp; - int remoteid; - int hostsize, port; - char *host; - char *err; - - remoteid = ssh_pkt_getuint32(pktin); - ssh_pkt_getstring(pktin, &host, &hostsize); - port = ssh_pkt_getuint32(pktin); - - pf.dhost = dupprintf("%.*s", hostsize, NULLTOEMPTY(host)); - pf.dport = port; - pfp = find234(ssh->rportfwds, &pf, NULL); - - if (pfp == NULL) { - logeventf(ssh, "Rejected remote port open request for %s:%d", - pf.dhost, port); - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, - PKT_INT, remoteid, PKT_END); - } else { - struct ssh_channel *c = snew(struct ssh_channel); - c->ssh = ssh; - - logeventf(ssh, "Received remote port open request for %s:%d", - pf.dhost, port); - err = pfd_connect(&c->u.pfd.pf, pf.dhost, port, - c, ssh->conf, pfp->pfrec->addressfamily); - if (err != NULL) { - logeventf(ssh, "Port open failed: %s", err); - sfree(err); - sfree(c); - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, - PKT_INT, remoteid, PKT_END); - } else { - ssh_channel_init(c); - c->remoteid = remoteid; - c->halfopen = FALSE; - c->type = CHAN_SOCKDATA; /* identify channel type */ - send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, - PKT_INT, c->remoteid, PKT_INT, - c->localid, PKT_END); - logevent("Forwarded port opened successfully"); - } - } - - sfree(pf.dhost); -} - -static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (c && c->type == CHAN_SOCKDATA) { - c->remoteid = ssh_pkt_getuint32(pktin); - c->halfopen = FALSE; - c->throttling_conn = 0; - pfd_confirm(c->u.pfd.pf); - } - - if (c && c->pending_eof) { - /* - * We have a pending close on this channel, - * which we decided on before the server acked - * the channel open. So now we know the - * remoteid, we can close it again. - */ - ssh_channel_try_eof(c); - } -} - -static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (c && c->type == CHAN_SOCKDATA) { - logevent("Forwarded connection refused by server"); - pfd_close(c->u.pfd.pf); - del234(ssh->channels, c); - sfree(c); - } -} - -static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin) -{ - /* Remote side closes a channel. */ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (c) { - - if (pktin->type == SSH1_MSG_CHANNEL_CLOSE) { - /* - * Received CHANNEL_CLOSE, which we translate into - * outgoing EOF. - */ - ssh_channel_got_eof(c); - } - - if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION && - !(c->closes & CLOSES_RCVD_CLOSE)) { - - if (!(c->closes & CLOSES_SENT_EOF)) { - bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %u" - " for which we never sent CHANNEL_CLOSE\n", - c->localid)); - } - - c->closes |= CLOSES_RCVD_CLOSE; - } - - if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) && - !(c->closes & CLOSES_SENT_CLOSE)) { - send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, - PKT_INT, c->remoteid, PKT_END); - c->closes |= CLOSES_SENT_CLOSE; - } - - if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) - ssh_channel_destroy(c); - } -} - -/* - * Handle incoming data on an SSH-1 or SSH-2 agent-forwarding channel. - */ -static int ssh_agent_channel_data(struct ssh_channel *c, char *data, - int length) -{ - bufchain_add(&c->u.a.inbuffer, data, length); - ssh_agentf_try_forward(c); - - /* - * 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 (c->u.a.pending ? bufchain_size(&c->u.a.inbuffer) : 0); -} - -static int ssh_channel_data(struct ssh_channel *c, int is_stderr, - char *data, int length) -{ - switch (c->type) { - case CHAN_MAINSESSION: - return from_backend(c->ssh->frontend, is_stderr, data, length); - case CHAN_X11: - return x11_send(c->u.x11.xconn, data, length); - case CHAN_SOCKDATA: - return pfd_send(c->u.pfd.pf, data, length); - case CHAN_AGENT: - return ssh_agent_channel_data(c, data, length); - } - return 0; -} - -static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin) -{ - /* Data sent down one of our channels. */ - char *p; - int len; - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - ssh_pkt_getstring(pktin, &p, &len); - - if (c) { - int bufsize = ssh_channel_data(c, FALSE, p, len); - if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { - c->throttling_conn = 1; - ssh_throttle_conn(ssh, +1); - } - } -} - -static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin) -{ - ssh->exitcode = ssh_pkt_getuint32(pktin); - logeventf(ssh, "Server sent command exit status %d", ssh->exitcode); - send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END); - /* - * In case `helpful' firewalls or proxies tack - * extra human-readable text on the end of the - * session which we might mistake for another - * encrypted packet, we close the session once - * we've sent EXIT_CONFIRMATION. - */ - ssh_disconnect(ssh, NULL, NULL, 0, TRUE); -} - -/* Helper function to deal with sending tty modes for REQUEST_PTY */ -static void ssh1_send_ttymode(void *data, - const struct ssh_ttymode *mode, char *val) -{ - struct Packet *pktout = (struct Packet *)data; - unsigned int arg = 0; - - switch (mode->type) { - case TTY_OP_CHAR: - arg = ssh_tty_parse_specchar(val); - break; - case TTY_OP_BOOL: - arg = ssh_tty_parse_boolean(val); - break; - } - ssh2_pkt_addbyte(pktout, mode->opcode); - ssh2_pkt_addbyte(pktout, arg); -} - -int ssh_agent_forwarding_permitted(Ssh ssh) -{ - return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists(); -} - -static void do_ssh1_connection(Ssh ssh, const unsigned char *in, int inlen, - struct Packet *pktin) -{ - crBegin(ssh->do_ssh1_connection_crstate); - - ssh->packet_dispatch[SSH1_SMSG_STDOUT_DATA] = - ssh->packet_dispatch[SSH1_SMSG_STDERR_DATA] = - ssh1_smsg_stdout_stderr_data; - - ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_CONFIRMATION] = - ssh1_msg_channel_open_confirmation; - ssh->packet_dispatch[SSH1_MSG_CHANNEL_OPEN_FAILURE] = - ssh1_msg_channel_open_failure; - ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE] = - ssh->packet_dispatch[SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION] = - ssh1_msg_channel_close; - ssh->packet_dispatch[SSH1_MSG_CHANNEL_DATA] = ssh1_msg_channel_data; - ssh->packet_dispatch[SSH1_SMSG_EXIT_STATUS] = ssh1_smsg_exit_status; - - if (ssh_agent_forwarding_permitted(ssh)) { - logevent("Requesting agent forwarding"); - send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END); - do { - crReturnV; - } while (!pktin); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - logevent("Agent forwarding refused"); - } else { - logevent("Agent forwarding enabled"); - ssh->agentfwd_enabled = TRUE; - ssh->packet_dispatch[SSH1_SMSG_AGENT_OPEN] = ssh1_smsg_agent_open; - } - } - - if (conf_get_int(ssh->conf, CONF_x11_forward)) { - ssh->x11disp = - x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), - ssh->conf); - if (!ssh->x11disp) { - /* FIXME: return an error message from x11_setup_display */ - logevent("X11 forwarding not enabled: unable to" - " initialise X display"); - } else { - ssh->x11auth = x11_invent_fake_auth - (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); - ssh->x11auth->disp = ssh->x11disp; - - logevent("Requesting X11 forwarding"); - if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { - send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, - PKT_STR, ssh->x11auth->protoname, - PKT_STR, ssh->x11auth->datastring, - PKT_INT, ssh->x11disp->screennum, - PKT_END); - } else { - send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, - PKT_STR, ssh->x11auth->protoname, - PKT_STR, ssh->x11auth->datastring, - PKT_END); - } - do { - crReturnV; - } while (!pktin); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - logevent("X11 forwarding refused"); - } else { - logevent("X11 forwarding enabled"); - ssh->X11_fwd_enabled = TRUE; - ssh->packet_dispatch[SSH1_SMSG_X11_OPEN] = ssh1_smsg_x11_open; - } - } - } - - ssh_setup_portfwd(ssh, ssh->conf); - ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open; - - if (!conf_get_int(ssh->conf, CONF_nopty)) { - struct Packet *pkt; - /* Unpick the terminal-speed string. */ - /* XXX perhaps we should allow no speeds to be sent. */ - ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); - /* Send the pty request. */ - pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY); - ssh_pkt_addstring(pkt, conf_get_str(ssh->conf, CONF_termtype)); - ssh_pkt_adduint32(pkt, ssh->term_height); - ssh_pkt_adduint32(pkt, ssh->term_width); - ssh_pkt_adduint32(pkt, 0); /* width in pixels */ - ssh_pkt_adduint32(pkt, 0); /* height in pixels */ - parse_ttymodes(ssh, ssh1_send_ttymode, (void *)pkt); - ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED); - ssh_pkt_adduint32(pkt, ssh->ispeed); - ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED); - ssh_pkt_adduint32(pkt, ssh->ospeed); - ssh_pkt_addbyte(pkt, SSH_TTY_OP_END); - s_wrpkt(ssh, pkt); - ssh->state = SSH_STATE_INTERMED; - do { - crReturnV; - } while (!pktin); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused to allocate pty\r\n"); - ssh->editing = ssh->echoing = 1; - } else { - logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", - ssh->ospeed, ssh->ispeed); - ssh->got_pty = TRUE; - } - } else { - ssh->editing = ssh->echoing = 1; - } - - if (conf_get_int(ssh->conf, CONF_compression)) { - send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END); - do { - crReturnV; - } while (!pktin); - if (pktin->type != SSH1_SMSG_SUCCESS - && pktin->type != SSH1_SMSG_FAILURE) { - bombout(("Protocol confusion")); - crStopV; - } else if (pktin->type == SSH1_SMSG_FAILURE) { - c_write_str(ssh, "Server refused to compress\r\n"); - } - logevent("Started compression"); - ssh->v1_compressing = TRUE; - ssh->cs_comp_ctx = zlib_compress_init(); - logevent("Initialised zlib (RFC1950) compression"); - ssh->sc_comp_ctx = zlib_decompress_init(); - logevent("Initialised zlib (RFC1950) decompression"); - } - - /* - * Start the shell or command. - * - * Special case: if the first-choice command is an SSH-2 - * subsystem (hence not usable here) and the second choice - * exists, we fall straight back to that. - */ - { - char *cmd = conf_get_str(ssh->conf, CONF_remote_cmd); - - if (conf_get_int(ssh->conf, CONF_ssh_subsys) && - conf_get_str(ssh->conf, CONF_remote_cmd2)) { - cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); - ssh->fallback_cmd = TRUE; - } - if (*cmd) - send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END); - else - send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END); - logevent("Started session"); - } - - ssh->state = SSH_STATE_SESSION; - if (ssh->size_needed) - ssh_size(ssh, ssh->term_width, ssh->term_height); - if (ssh->eof_needed) - ssh_special(ssh, TS_EOF); - - if (ssh->ldisc) - ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */ - ssh->send_ok = 1; - ssh->channels = newtree234(ssh_channelcmp); - while (1) { - - /* - * By this point, most incoming packets are already being - * handled by the dispatch table, and we need only pay - * attention to the unusual ones. - */ - - crReturnV; - if (pktin) { - if (pktin->type == SSH1_SMSG_SUCCESS) { - /* may be from EXEC_SHELL on some servers */ - } else if (pktin->type == SSH1_SMSG_FAILURE) { - /* may be from EXEC_SHELL on some servers - * if no pty is available or in other odd cases. Ignore */ - } else { - bombout(("Strange packet received: type %d", pktin->type)); - crStopV; - } - } else { - while (inlen > 0) { - int len = min(inlen, 512); - send_packet(ssh, SSH1_CMSG_STDIN_DATA, - PKT_INT, len, PKT_DATA, in, len, - PKT_END); - in += len; - inlen -= len; - } - } - } - - crFinishV; -} - -/* - * Handle the top-level SSH-2 protocol. - */ -static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) -{ - char *msg; - int msglen; - - ssh_pkt_getstring(pktin, &msg, &msglen); - logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg)); -} - -static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) -{ - /* log reason code in disconnect message */ - char *msg; - int msglen; - - ssh_pkt_getstring(pktin, &msg, &msglen); - bombout(("Server sent disconnect message:\n\"%.*s\"", - msglen, NULLTOEMPTY(msg))); -} - -static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) -{ - /* Do nothing, because we're ignoring it! Duhh. */ -} - -static void ssh1_protocol_setup(Ssh ssh) -{ - int i; - - /* - * Most messages are handled by the coroutines. - */ - for (i = 0; i < 256; i++) - ssh->packet_dispatch[i] = NULL; - - /* - * These special message types we install handlers for. - */ - ssh->packet_dispatch[SSH1_MSG_DISCONNECT] = ssh1_msg_disconnect; - ssh->packet_dispatch[SSH1_MSG_IGNORE] = ssh_msg_ignore; - ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug; -} - -static void ssh1_protocol(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin) -{ - const unsigned char *in = (const unsigned char *)vin; - if (ssh->state == SSH_STATE_CLOSED) - return; - - if (pktin && ssh->packet_dispatch[pktin->type]) { - ssh->packet_dispatch[pktin->type](ssh, pktin); - return; - } - - if (!ssh->protocol_initial_phase_done) { - if (do_ssh1_login(ssh, in, inlen, pktin)) - ssh->protocol_initial_phase_done = TRUE; - else - return; - } - - do_ssh1_connection(ssh, in, inlen, pktin); -} - -/* - * Utility routines for decoding comma-separated strings in KEXINIT. - */ -static int first_in_commasep_string(char const *needle, char const *haystack, - int haylen) -{ - int needlen; - if (!needle || !haystack) /* protect against null pointers */ - return 0; - needlen = strlen(needle); - - if (haylen >= needlen && /* haystack is long enough */ - !memcmp(needle, haystack, needlen) && /* initial match */ - (haylen == needlen || haystack[needlen] == ',') - /* either , or EOS follows */ - ) - return 1; - return 0; -} - -static int in_commasep_string(char const *needle, char const *haystack, - int haylen) -{ - char *p; - - if (!needle || !haystack) /* protect against null pointers */ - return 0; - /* - * Is it at the start of the string? - */ - if (first_in_commasep_string(needle, haystack, haylen)) - return 1; - /* - * If not, search for the next comma and resume after that. - * If no comma found, terminate. - */ - p = memchr(haystack, ',', haylen); - if (!p) return 0; - /* + 1 to skip over comma */ - return in_commasep_string(needle, p + 1, haylen - (p + 1 - haystack)); -} - -/* - * Add a value to the comma-separated string at the end of the packet. - */ -static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data) -{ - if (pkt->length - pkt->savedpos > 0) - ssh_pkt_addstring_str(pkt, ","); - ssh_pkt_addstring_str(pkt, data); -} - - -/* - * SSH-2 key derivation (RFC 4253 section 7.2). - */ -static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, - char chr, int keylen) -{ - const struct ssh_hash *h = ssh->kex->hash; - int keylen_padded; - unsigned char *key; - void *s, *s2; - - if (keylen == 0) - return NULL; - - /* Round up to the next multiple of hash length. */ - keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen; - - key = snewn(keylen_padded, unsigned char); - - /* First hlen bytes. */ - s = h->init(); - if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) - hash_mpint(h, s, K); - h->bytes(s, H, h->hlen); - h->bytes(s, &chr, 1); - h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len); - h->final(s, key); - - /* Subsequent blocks of hlen bytes. */ - if (keylen_padded > h->hlen) { - int offset; - - s = h->init(); - if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY)) - hash_mpint(h, s, K); - h->bytes(s, H, h->hlen); - - for (offset = h->hlen; offset < keylen_padded; offset += h->hlen) { - h->bytes(s, key + offset - h->hlen, h->hlen); - s2 = h->copy(s); - h->final(s2, key + offset); - } - - h->free(s); - } - - /* Now clear any extra bytes of key material beyond the length - * we're officially returning, because the caller won't know to - * smemclr those. */ - if (keylen_padded > keylen) - smemclr(key + keylen, keylen_padded - keylen); - - return key; -} - -/* - * Structure for constructing KEXINIT algorithm lists. - */ -#define MAXKEXLIST 16 -struct kexinit_algorithm { - const char *name; - union { - struct { - const struct ssh_kex *kex; - int warn; - } kex; - struct { - const struct ssh_signkey *hostkey; - int warn; - } hk; - struct { - const struct ssh2_cipher *cipher; - int warn; - } cipher; - struct { - const struct ssh_mac *mac; - int etm; - } mac; - const struct ssh_compress *comp; - } u; -}; - -/* - * 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]; - } - assert(!"No space in KEXINIT list"); - return NULL; -} - -/* - * Handle the SSH-2 transport layer. - */ -static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin) -{ - const unsigned char *in = (const unsigned char *)vin; - enum kexlist { - KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER, - KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP, - NKEXLIST - }; - const char * 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" }; - struct do_ssh2_transport_state { - int crLine; - int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher; - Bignum p, g, e, f, K; - void *our_kexinit; - int our_kexinitlen; - int kex_init_value, kex_reply_value; - const struct ssh_mac *const *maclist; - int nmacs; - const struct ssh2_cipher *cscipher_tobe; - const struct ssh2_cipher *sccipher_tobe; - const struct ssh_mac *csmac_tobe; - const struct ssh_mac *scmac_tobe; - int csmac_etm_tobe, scmac_etm_tobe; - const struct ssh_compress *cscomp_tobe; - const struct ssh_compress *sccomp_tobe; - char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint; - int hostkeylen, siglen, rsakeylen; - void *hkey; /* actual host key */ - void *rsakey; /* for RSA kex */ - void *eckey; /* for ECDH kex */ - unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; - int n_preferred_kex; - const struct ssh_kexes *preferred_kex[KEX_MAX]; - int n_preferred_hk; - int preferred_hk[HK_MAX]; - int n_preferred_ciphers; - const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; - const struct ssh_compress *preferred_comp; - int userauth_succeeded; /* for delayed compression */ - int pending_compression; - int got_session_id, activated_authconn; - struct Packet *pktout; - int dlgret; - int guessok; - int ignorepkt; - struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; - }; - crState(do_ssh2_transport_state); - - assert(!ssh->bare_connection); - assert(ssh->version == 2); - - crBeginState; - - s->cscipher_tobe = s->sccipher_tobe = NULL; - s->csmac_tobe = s->scmac_tobe = NULL; - s->cscomp_tobe = s->sccomp_tobe = NULL; - - s->got_session_id = s->activated_authconn = FALSE; - s->userauth_succeeded = FALSE; - s->pending_compression = FALSE; - - /* - * Be prepared to work around the buggy MAC problem. - */ - if (ssh->remote_bugs & BUG_SSH2_HMAC) - s->maclist = buggymacs, s->nmacs = lenof(buggymacs); - else - s->maclist = macs, s->nmacs = lenof(macs); - - begin_key_exchange: - ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; - { - int i, j, k, warn; - struct kexinit_algorithm *alg; - - /* - * Set up the preferred key exchange. (NULL => warn below here) - */ - s->n_preferred_kex = 0; - for (i = 0; i < KEX_MAX; i++) { - switch (conf_get_int_int(ssh->conf, CONF_ssh_kexlist, i)) { - case KEX_DHGEX: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_diffiehellman_gex; - break; - case KEX_DHGROUP14: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_diffiehellman_group14; - break; - case KEX_DHGROUP1: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_diffiehellman_group1; - break; - case KEX_RSA: - s->preferred_kex[s->n_preferred_kex++] = - &ssh_rsa_kex; - break; - case KEX_ECDH: - s->preferred_kex[s->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) { - s->preferred_kex[s->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. - */ - s->n_preferred_hk = 0; - for (i = 0; i < HK_MAX; i++) { - int id = conf_get_int_int(ssh->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) - s->preferred_hk[s->n_preferred_hk++] = id; - } - - /* - * Set up the preferred ciphers. (NULL => warn below here) - */ - s->n_preferred_ciphers = 0; - for (i = 0; i < CIPHER_MAX; i++) { - switch (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i)) { - case CIPHER_BLOWFISH: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish; - break; - case CIPHER_DES: - if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc)) { - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des; - } - break; - case CIPHER_3DES: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des; - break; - case CIPHER_AES: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes; - break; - case CIPHER_ARCFOUR: - s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour; - break; - case CIPHER_CHACHA20: - s->preferred_ciphers[s->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) { - s->preferred_ciphers[s->n_preferred_ciphers++] = NULL; - } - break; - } - } - - /* - * Set up preferred compression. - */ - if (conf_get_int(ssh->conf, CONF_compression)) - s->preferred_comp = &ssh_zlib; - else - s->preferred_comp = &ssh_comp_none; - - /* - * Enable queueing of outgoing auth- or connection-layer - * packets while we are in the middle of a key exchange. - */ - ssh->queueing = TRUE; - - /* - * Flag that KEX is in progress. - */ - ssh->kex_in_progress = TRUE; - - for (i = 0; i < NKEXLIST; i++) - for (j = 0; j < MAXKEXLIST; j++) - s->kexlists[i][j].name = NULL; - /* List key exchange algorithms. */ - warn = FALSE; - for (i = 0; i < s->n_preferred_kex; i++) { - const struct ssh_kexes *k = s->preferred_kex[i]; - if (!k) warn = TRUE; - else for (j = 0; j < k->nkexes; j++) { - alg = ssh2_kexinit_addalg(s->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 (!s->got_session_id) { - /* - * In the first key exchange, we list all the algorithms - * we're prepared to cope with, but 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 < s->n_preferred_hk; i++) { - if (s->preferred_hk[i] == HK_WARN) - warn = TRUE; - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].id != s->preferred_hk[i]) - continue; - if (have_ssh_host_key(ssh->savedhost, ssh->savedport, - hostkey_algs[j].alg->keytype)) { - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - hostkey_algs[j].alg->name); - alg->u.hk.hostkey = hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } - warn = FALSE; - for (i = 0; i < s->n_preferred_hk; i++) { - if (s->preferred_hk[i] == HK_WARN) - warn = TRUE; - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].id != s->preferred_hk[i]) - continue; - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - hostkey_algs[j].alg->name); - alg->u.hk.hostkey = hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } else { - /* - * In subsequent key exchanges, we list only the kex - * algorithm that was selected in the first key exchange, - * so that we keep getting the same host key and hence - * don't have to interrupt the user's session to ask for - * reverification. - */ - assert(ssh->kex); - alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY], - ssh->hostkey->name); - alg->u.hk.hostkey = ssh->hostkey; - alg->u.hk.warn = FALSE; - } - /* List encryption algorithms (client->server then server->client). */ - for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { - warn = FALSE; -#ifdef FUZZING - alg = ssh2_kexinit_addalg(s->kexlists[k], "none"); - alg->u.cipher.cipher = NULL; - alg->u.cipher.warn = warn; -#endif /* FUZZING */ - for (i = 0; i < s->n_preferred_ciphers; i++) { - const struct ssh2_ciphers *c = s->preferred_ciphers[i]; - if (!c) warn = TRUE; - else for (j = 0; j < c->nciphers; j++) { - alg = ssh2_kexinit_addalg(s->kexlists[k], - c->list[j]->name); - alg->u.cipher.cipher = c->list[j]; - alg->u.cipher.warn = warn; - } - } - } - /* List MAC algorithms (client->server then server->client). */ - for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { -#ifdef FUZZING - alg = ssh2_kexinit_addalg(s->kexlists[j], "none"); - alg->u.mac.mac = NULL; - alg->u.mac.etm = FALSE; -#endif /* FUZZING */ - for (i = 0; i < s->nmacs; i++) { - alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name); - alg->u.mac.mac = s->maclist[i]; - alg->u.mac.etm = FALSE; - } - for (i = 0; i < s->nmacs; i++) - /* For each MAC, there may also be an ETM version, - * which we list second. */ - if (s->maclist[i]->etm_name) { - alg = ssh2_kexinit_addalg(s->kexlists[j], - s->maclist[i]->etm_name); - alg->u.mac.mac = s->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(s->kexlists[j], s->preferred_comp->name); - alg->u.comp = s->preferred_comp; - /* We don't even list delayed versions of algorithms until - * they're allowed to be used, to avoid a race. See the end of - * this function. */ - if (s->userauth_succeeded && s->preferred_comp->delayed_name) { - alg = ssh2_kexinit_addalg(s->kexlists[j], - s->preferred_comp->delayed_name); - alg->u.comp = s->preferred_comp; - } - for (i = 0; i < lenof(compressions); i++) { - const struct ssh_compress *c = compressions[i]; - alg = ssh2_kexinit_addalg(s->kexlists[j], c->name); - alg->u.comp = c; - if (s->userauth_succeeded && c->delayed_name) { - alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name); - alg->u.comp = c; - } - } - } - /* - * Construct and send our key exchange packet. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT); - for (i = 0; i < 16; i++) - ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte()); - for (i = 0; i < NKEXLIST; i++) { - ssh2_pkt_addstring_start(s->pktout); - for (j = 0; j < MAXKEXLIST; j++) { - if (s->kexlists[i][j].name == NULL) break; - ssh2_pkt_addstring_commasep(s->pktout, s->kexlists[i][j].name); - } - } - /* List client->server languages. Empty list. */ - ssh2_pkt_addstring_start(s->pktout); - /* List server->client languages. Empty list. */ - ssh2_pkt_addstring_start(s->pktout); - /* First KEX packet does _not_ follow, because we're not that brave. */ - ssh2_pkt_addbool(s->pktout, FALSE); - /* Reserved. */ - ssh2_pkt_adduint32(s->pktout, 0); - } - - s->our_kexinitlen = s->pktout->length - 5; - s->our_kexinit = snewn(s->our_kexinitlen, unsigned char); - memcpy(s->our_kexinit, s->pktout->data + 5, s->our_kexinitlen); - - ssh2_pkt_send_noqueue(ssh, s->pktout); - - if (!pktin) - crWaitUntilV(pktin); - - /* - * Now examine the other side's KEXINIT to see what we're up - * to. - */ - { - char *str; - int i, j, len; - - if (pktin->type != SSH2_MSG_KEXINIT) { - bombout(("expected key exchange packet from server")); - crStopV; - } - ssh->kex = NULL; - ssh->hostkey = NULL; - s->cscipher_tobe = NULL; - s->sccipher_tobe = NULL; - s->csmac_tobe = NULL; - s->scmac_tobe = NULL; - s->cscomp_tobe = NULL; - s->sccomp_tobe = NULL; - s->warn_kex = s->warn_hk = FALSE; - s->warn_cscipher = s->warn_sccipher = FALSE; - - pktin->savedpos += 16; /* skip garbage cookie */ - - s->guessok = FALSE; - for (i = 0; i < NKEXLIST; i++) { - ssh_pkt_getstring(pktin, &str, &len); - if (!str) { - bombout(("KEXINIT packet was incomplete")); - crStopV; - } - - /* If we've already selected a cipher which requires a - * particular MAC, then just select that, and don't even - * bother looking through the server's KEXINIT string for - * MACs. */ - if (i == KEXLIST_CSMAC && s->cscipher_tobe && - s->cscipher_tobe->required_mac) { - s->csmac_tobe = s->cscipher_tobe->required_mac; - s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name); - goto matched; - } - if (i == KEXLIST_SCMAC && s->sccipher_tobe && - s->sccipher_tobe->required_mac) { - s->scmac_tobe = s->sccipher_tobe->required_mac; - s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name); - goto matched; - } - - for (j = 0; j < MAXKEXLIST; j++) { - struct kexinit_algorithm *alg = &s->kexlists[i][j]; - if (alg->name == NULL) break; - if (in_commasep_string(alg->name, str, len)) { - /* We've found a matching algorithm. */ - if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) { - /* Check if we might need to ignore first kex pkt */ - if (j != 0 || - !first_in_commasep_string(alg->name, str, len)) - s->guessok = FALSE; - } - if (i == KEXLIST_KEX) { - ssh->kex = alg->u.kex.kex; - s->warn_kex = alg->u.kex.warn; - } else if (i == KEXLIST_HOSTKEY) { - ssh->hostkey = alg->u.hk.hostkey; - s->warn_hk = alg->u.hk.warn; - } else if (i == KEXLIST_CSCIPHER) { - s->cscipher_tobe = alg->u.cipher.cipher; - s->warn_cscipher = alg->u.cipher.warn; - } else if (i == KEXLIST_SCCIPHER) { - s->sccipher_tobe = alg->u.cipher.cipher; - s->warn_sccipher = alg->u.cipher.warn; - } else if (i == KEXLIST_CSMAC) { - s->csmac_tobe = alg->u.mac.mac; - s->csmac_etm_tobe = alg->u.mac.etm; - } else if (i == KEXLIST_SCMAC) { - s->scmac_tobe = alg->u.mac.mac; - s->scmac_etm_tobe = alg->u.mac.etm; - } else if (i == KEXLIST_CSCOMP) { - s->cscomp_tobe = alg->u.comp; - } else if (i == KEXLIST_SCCOMP) { - s->sccomp_tobe = alg->u.comp; - } - goto matched; - } - if ((i == KEXLIST_CSCOMP || i == KEXLIST_SCCOMP) && - in_commasep_string(alg->u.comp->delayed_name, str, len)) - s->pending_compression = TRUE; /* try this later */ - } - bombout(("Couldn't agree a %s (available: %.*s)", - kexlist_descr[i], len, str)); - crStopV; - matched:; - - if (i == KEXLIST_HOSTKEY) { - int j; - - /* - * 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. - */ - ssh->n_uncert_hostkeys = 0; - - for (j = 0; j < lenof(hostkey_algs); j++) { - if (hostkey_algs[j].alg != ssh->hostkey && - in_commasep_string(hostkey_algs[j].alg->name, - str, len) && - !have_ssh_host_key(ssh->savedhost, ssh->savedport, - hostkey_algs[j].alg->keytype)) { - ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j; - } - } - } - } - - if (s->pending_compression) { - logevent("Server supports delayed compression; " - "will try this later"); - } - ssh_pkt_getstring(pktin, &str, &len); /* client->server language */ - ssh_pkt_getstring(pktin, &str, &len); /* server->client language */ - s->ignorepkt = ssh2_pkt_getbool(pktin) && !s->guessok; - - ssh->exhash = ssh->kex->hash->init(); - hash_string(ssh->kex->hash, ssh->exhash, ssh->v_c, strlen(ssh->v_c)); - hash_string(ssh->kex->hash, ssh->exhash, ssh->v_s, strlen(ssh->v_s)); - hash_string(ssh->kex->hash, ssh->exhash, - s->our_kexinit, s->our_kexinitlen); - sfree(s->our_kexinit); - /* Include the type byte in the hash of server's KEXINIT */ - hash_string(ssh->kex->hash, ssh->exhash, - pktin->body - 1, pktin->length + 1); - - if (s->warn_kex) { - ssh_set_frozen(ssh, 1); - s->dlgret = askalg(ssh->frontend, "key-exchange algorithm", - ssh->kex->name, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while" - " waiting for user response")); - crStopV; - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at kex warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->warn_hk) { - int j, k; - char *betteralgs; - - ssh_set_frozen(ssh, 1); - - /* - * 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*, use the askhk mechanism, - * and list the host keys we could usefully cross-certify. - * Otherwise, use askalg for the standard wording. - */ - betteralgs = NULL; - for (j = 0; j < ssh->n_uncert_hostkeys; j++) { - const struct ssh_signkey_with_user_pref_id *hktype = - &hostkey_algs[ssh->uncert_hostkeys[j]]; - int better = FALSE; - for (k = 0; k < HK_MAX; k++) { - int id = conf_get_int_int(ssh->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->name, - (const char *)NULL); - sfree(old_ba); - } else { - betteralgs = dupstr(hktype->alg->name); - } - } - } - if (betteralgs) { - s->dlgret = askhk(ssh->frontend, ssh->hostkey->name, - betteralgs, ssh_dialog_callback, ssh); - sfree(betteralgs); - } else { - s->dlgret = askalg(ssh->frontend, "host key type", - ssh->hostkey->name, - ssh_dialog_callback, ssh); - } - if (s->dlgret < 0) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while" - " waiting for user response")); - crStopV; - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at host key warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->warn_cscipher) { - ssh_set_frozen(ssh, 1); - s->dlgret = askalg(ssh->frontend, - "client-to-server cipher", - s->cscipher_tobe->name, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while" - " waiting for user response")); - crStopV; - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at cipher warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->warn_sccipher) { - ssh_set_frozen(ssh, 1); - s->dlgret = askalg(ssh->frontend, - "server-to-client cipher", - s->sccipher_tobe->name, - ssh_dialog_callback, ssh); - if (s->dlgret < 0) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while" - " waiting for user response")); - crStopV; - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "User aborted at cipher warning", NULL, - 0, TRUE); - crStopV; - } - } - - if (s->ignorepkt) /* first_kex_packet_follows */ - crWaitUntilV(pktin); /* Ignore packet */ - } - - if (ssh->kex->main_type == KEXTYPE_DH) { - /* - * Work out the number of bits of key we will need from the - * key exchange. We start with the maximum key length of - * either cipher... - */ - { - int csbits, scbits; - - csbits = s->cscipher_tobe ? s->cscipher_tobe->real_keybits : 0; - scbits = s->sccipher_tobe ? s->sccipher_tobe->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 > ssh->kex->hash->hlen * 8) - s->nbits = ssh->kex->hash->hlen * 8; - - /* - * If we're doing Diffie-Hellman group exchange, start by - * requesting a group. - */ - if (dh_is_gex(ssh->kex)) { - logevent("Doing Diffie-Hellman group exchange"); - ssh->pkt_kctx = SSH2_PKTCTX_DHGEX; - /* - * Work out how big a DH group we will need to allow that - * much data. - */ - s->pbits = 512 << ((s->nbits - 1) / 64); - if (s->pbits < DH_MIN_SIZE) - s->pbits = DH_MIN_SIZE; - if (s->pbits > DH_MAX_SIZE) - s->pbits = DH_MAX_SIZE; - if ((ssh->remote_bugs & BUG_SSH2_OLDGEX)) { - s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); - ssh2_pkt_adduint32(s->pktout, s->pbits); - } else { - s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST); - ssh2_pkt_adduint32(s->pktout, DH_MIN_SIZE); - ssh2_pkt_adduint32(s->pktout, s->pbits); - ssh2_pkt_adduint32(s->pktout, DH_MAX_SIZE); - } - ssh2_pkt_send_noqueue(ssh, s->pktout); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { - bombout(("expected key exchange group packet from server")); - crStopV; - } - s->p = ssh2_pkt_getmp(pktin); - s->g = ssh2_pkt_getmp(pktin); - if (!s->p || !s->g) { - bombout(("unable to read mp-ints from incoming group packet")); - crStopV; - } - ssh->kex_ctx = dh_setup_gex(s->p, s->g); - s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT; - s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY; - } else { - ssh->pkt_kctx = SSH2_PKTCTX_DHGROUP; - ssh->kex_ctx = dh_setup_group(ssh->kex); - s->kex_init_value = SSH2_MSG_KEXDH_INIT; - s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; - logeventf(ssh, "Using Diffie-Hellman with standard group \"%s\"", - ssh->kex->groupname); - } - - logeventf(ssh, "Doing Diffie-Hellman key exchange with hash %s", - ssh->kex->hash->text_name); - /* - * Now generate and send e for Diffie-Hellman. - */ - set_busy_status(ssh->frontend, BUSY_CPU); /* this can take a while */ - s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2); - s->pktout = ssh2_pkt_init(s->kex_init_value); - ssh2_pkt_addmp(s->pktout, s->e); - ssh2_pkt_send_noqueue(ssh, s->pktout); - - set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */ - crWaitUntilV(pktin); - if (pktin->type != s->kex_reply_value) { - bombout(("expected key exchange reply packet from server")); - crStopV; - } - set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */ - ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); - if (!s->hostkeydata) { - bombout(("unable to parse key exchange reply packet")); - crStopV; - } - s->hkey = ssh->hostkey->newkey(ssh->hostkey, - s->hostkeydata, s->hostkeylen); - s->f = ssh2_pkt_getmp(pktin); - if (!s->f) { - bombout(("unable to parse key exchange reply packet")); - crStopV; - } - ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); - if (!s->sigdata) { - bombout(("unable to parse key exchange reply packet")); - crStopV; - } - - { - const char *err = dh_validate_f(ssh->kex_ctx, s->f); - if (err) { - bombout(("key exchange reply failed validation: %s", err)); - crStopV; - } - } - s->K = dh_find_K(ssh->kex_ctx, s->f); - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - set_busy_status(ssh->frontend, BUSY_NOT); - - hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); - if (dh_is_gex(ssh->kex)) { - if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) - hash_uint32(ssh->kex->hash, ssh->exhash, DH_MIN_SIZE); - hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits); - if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX)) - hash_uint32(ssh->kex->hash, ssh->exhash, DH_MAX_SIZE); - hash_mpint(ssh->kex->hash, ssh->exhash, s->p); - hash_mpint(ssh->kex->hash, ssh->exhash, s->g); - } - hash_mpint(ssh->kex->hash, ssh->exhash, s->e); - hash_mpint(ssh->kex->hash, ssh->exhash, s->f); - - dh_cleanup(ssh->kex_ctx); - freebn(s->f); - if (dh_is_gex(ssh->kex)) { - freebn(s->g); - freebn(s->p); - } - } else if (ssh->kex->main_type == KEXTYPE_ECDH) { - - logeventf(ssh, "Doing ECDH key exchange with curve %s and hash %s", - ssh_ecdhkex_curve_textname(ssh->kex), - ssh->kex->hash->text_name); - ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX; - - s->eckey = ssh_ecdhkex_newkey(ssh->kex); - if (!s->eckey) { - bombout(("Unable to generate key for ECDH")); - crStopV; - } - - { - char *publicPoint; - int publicPointLength; - publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength); - if (!publicPoint) { - ssh_ecdhkex_freekey(s->eckey); - bombout(("Unable to encode public key for ECDH")); - crStopV; - } - s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, publicPoint, publicPointLength); - sfree(publicPoint); - } - - ssh2_pkt_send_noqueue(ssh, s->pktout); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { - ssh_ecdhkex_freekey(s->eckey); - bombout(("expected ECDH reply packet from server")); - crStopV; - } - - ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); - if (!s->hostkeydata) { - bombout(("unable to parse ECDH reply packet")); - crStopV; - } - hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen); - s->hkey = ssh->hostkey->newkey(ssh->hostkey, - s->hostkeydata, s->hostkeylen); - - { - char *publicPoint; - int publicPointLength; - publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength); - if (!publicPoint) { - ssh_ecdhkex_freekey(s->eckey); - bombout(("Unable to encode public key for ECDH hash")); - crStopV; - } - hash_string(ssh->kex->hash, ssh->exhash, - publicPoint, publicPointLength); - sfree(publicPoint); - } - - { - char *keydata; - int keylen; - ssh_pkt_getstring(pktin, &keydata, &keylen); - if (!keydata) { - bombout(("unable to parse ECDH reply packet")); - crStopV; - } - hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen); - s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen); - if (!s->K) { - ssh_ecdhkex_freekey(s->eckey); - bombout(("point received in ECDH was not valid")); - crStopV; - } - } - - ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); - if (!s->sigdata) { - bombout(("unable to parse key exchange reply packet")); - crStopV; - } - - ssh_ecdhkex_freekey(s->eckey); - } else { - logeventf(ssh, "Doing RSA key exchange with hash %s", - ssh->kex->hash->text_name); - ssh->pkt_kctx = SSH2_PKTCTX_RSAKEX; - /* - * RSA key exchange. First expect a KEXRSA_PUBKEY packet - * from the server. - */ - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { - bombout(("expected RSA public key packet from server")); - crStopV; - } - - ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen); - if (!s->hostkeydata) { - bombout(("unable to parse RSA public key packet")); - crStopV; - } - hash_string(ssh->kex->hash, ssh->exhash, - s->hostkeydata, s->hostkeylen); - s->hkey = ssh->hostkey->newkey(ssh->hostkey, - s->hostkeydata, s->hostkeylen); - - { - char *keydata; - ssh_pkt_getstring(pktin, &keydata, &s->rsakeylen); - if (!keydata) { - bombout(("unable to parse RSA public key packet")); - crStopV; - } - s->rsakeydata = snewn(s->rsakeylen, char); - memcpy(s->rsakeydata, keydata, s->rsakeylen); - } - - s->rsakey = ssh_rsakex_newkey(s->rsakeydata, s->rsakeylen); - if (!s->rsakey) { - sfree(s->rsakeydata); - bombout(("unable to parse RSA public key from server")); - crStopV; - } - - hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen); - - /* - * Next, set up a shared secret K, of precisely KLEN - - * 2*HLEN - 49 bits, where KLEN is the bit length of the - * RSA key modulus and HLEN is the bit length of the hash - * we're using. - */ - { - int klen = ssh_rsakex_klen(s->rsakey); - int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49); - int i, byte = 0; - unsigned char *kstr1, *kstr2, *outstr; - int kstr1len, kstr2len, outstrlen; - - s->K = bn_power_2(nbits - 1); - - for (i = 0; i < nbits; i++) { - if ((i & 7) == 0) { - byte = random_byte(); - } - bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); - } - - /* - * Encode this as an mpint. - */ - kstr1 = ssh2_mpint_fmt(s->K, &kstr1len); - kstr2 = snewn(kstr2len = 4 + kstr1len, unsigned char); - PUT_32BIT(kstr2, kstr1len); - memcpy(kstr2 + 4, kstr1, kstr1len); - - /* - * Encrypt it with the given RSA key. - */ - outstrlen = (klen + 7) / 8; - outstr = snewn(outstrlen, unsigned char); - ssh_rsakex_encrypt(ssh->kex->hash, kstr2, kstr2len, - outstr, outstrlen, s->rsakey); - - /* - * And send it off in a return packet. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_KEXRSA_SECRET); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, (char *)outstr, outstrlen); - ssh2_pkt_send_noqueue(ssh, s->pktout); - - hash_string(ssh->kex->hash, ssh->exhash, outstr, outstrlen); - - sfree(kstr2); - sfree(kstr1); - sfree(outstr); - } - - ssh_rsakex_freekey(s->rsakey); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_KEXRSA_DONE) { - sfree(s->rsakeydata); - bombout(("expected signature packet from server")); - crStopV; - } - - ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen); - if (!s->sigdata) { - bombout(("unable to parse signature packet")); - crStopV; - } - - sfree(s->rsakeydata); - } - - hash_mpint(ssh->kex->hash, ssh->exhash, s->K); - assert(ssh->kex->hash->hlen <= sizeof(s->exchange_hash)); - ssh->kex->hash->final(ssh->exhash, s->exchange_hash); - - ssh->kex_ctx = NULL; - -#if 0 - debug(("Exchange hash is:\n")); - dmemdump(s->exchange_hash, ssh->kex->hash->hlen); -#endif - - if (!s->hkey) { - bombout(("Server's host key is invalid")); - crStopV; - } - - if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen, - (char *)s->exchange_hash, - ssh->kex->hash->hlen)) { -#ifndef FUZZING - bombout(("Server's host key did not match the signature supplied")); - crStopV; -#endif - } - - s->keystr = ssh->hostkey->fmtkey(s->hkey); - 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(hostkey_algs); i++) { - if (hostkey_algs[i].alg == ssh->hostkey) - continue; - - for (j = 0; j < ssh->n_uncert_hostkeys; j++) - if (ssh->uncert_hostkeys[j] == i) - break; - - if (j < ssh->n_uncert_hostkeys) { - char *newlist; - if (list) - newlist = dupprintf("%s/%s", list, - hostkey_algs[i].alg->name); - else - newlist = dupprintf("%s", hostkey_algs[i].alg->name); - sfree(list); - list = newlist; - nkeys++; - } - } - if (list) { - logeventf(ssh, - "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.) - */ - s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); - logevent("Host key fingerprint is:"); - logevent(s->fingerprint); - /* First check against manually configured host keys. */ - s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint, - ssh->hostkey, s->hkey); - if (s->dlgret == 0) { /* did not match */ - bombout(("Host key did not appear in manually configured list")); - crStopV; - } else if (s->dlgret < 0) { /* none configured; use standard handling */ - ssh_set_frozen(ssh, 1); - s->dlgret = verify_ssh_host_key(ssh->frontend, - ssh->savedhost, ssh->savedport, - ssh->hostkey->keytype, s->keystr, - s->fingerprint, - ssh_dialog_callback, ssh); -#ifdef FUZZING - s->dlgret = 1; -#endif - if (s->dlgret < 0) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while waiting" - " for user host key response")); - crStopV; - } - } while (pktin || inlen > 0); - s->dlgret = ssh->user_response; - } - ssh_set_frozen(ssh, 0); - if (s->dlgret == 0) { - ssh_disconnect(ssh, "Aborted at host key verification", NULL, - 0, TRUE); - crStopV; - } - } - sfree(s->fingerprint); - /* - * Save this host key, to check against the one presented in - * subsequent rekeys. - */ - ssh->hostkey_str = s->keystr; - } else if (ssh->cross_certifying) { - s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey); - logevent("Storing additional host key for this host:"); - logevent(s->fingerprint); - sfree(s->fingerprint); - store_host_key(ssh->savedhost, ssh->savedport, - ssh->hostkey->keytype, s->keystr); - ssh->cross_certifying = FALSE; - /* - * Don't forget to store the new key as the one we'll be - * re-checking in future normal rekeys. - */ - ssh->hostkey_str = s->keystr; - } else { - /* - * In a rekey, we never present an interactive host key - * verification request to the user. Instead, we simply - * enforce that the key we're seeing this time is identical to - * the one we saw before. - */ - if (strcmp(ssh->hostkey_str, s->keystr)) { -#ifndef FUZZING - bombout(("Host key was different in repeat key exchange")); - crStopV; -#endif - } - sfree(s->keystr); - } - ssh->hostkey->freekey(s->hkey); - - /* - * The exchange hash from the very first key exchange is also - * the session id, used in session key construction and - * authentication. - */ - if (!s->got_session_id) { - assert(sizeof(s->exchange_hash) <= sizeof(ssh->v2_session_id)); - memcpy(ssh->v2_session_id, s->exchange_hash, - sizeof(s->exchange_hash)); - ssh->v2_session_id_len = ssh->kex->hash->hlen; - assert(ssh->v2_session_id_len <= sizeof(ssh->v2_session_id)); - s->got_session_id = TRUE; - } - - /* - * Send SSH2_MSG_NEWKEYS. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_NEWKEYS); - ssh2_pkt_send_noqueue(ssh, s->pktout); - ssh->outgoing_data_size = 0; /* start counting from here */ - - /* - * We've sent client NEWKEYS, so create and initialise - * client-to-server session keys. - */ - if (ssh->cs_cipher_ctx) - ssh->cscipher->free_context(ssh->cs_cipher_ctx); - ssh->cscipher = s->cscipher_tobe; - if (ssh->cscipher) ssh->cs_cipher_ctx = ssh->cscipher->make_context(); - - if (ssh->cs_mac_ctx) - ssh->csmac->free_context(ssh->cs_mac_ctx); - ssh->csmac = s->csmac_tobe; - ssh->csmac_etm = s->csmac_etm_tobe; - if (ssh->csmac) - ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx); - - if (ssh->cs_comp_ctx) - ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); - ssh->cscomp = s->cscomp_tobe; - ssh->cs_comp_ctx = ssh->cscomp->compress_init(); - - /* - * Set IVs on client-to-server keys. Here we use the exchange - * hash from the _first_ key exchange. - */ - if (ssh->cscipher) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C', - ssh->cscipher->padded_keybytes); - ssh->cscipher->setkey(ssh->cs_cipher_ctx, key); - smemclr(key, ssh->cscipher->padded_keybytes); - sfree(key); - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'A', - ssh->cscipher->blksize); - ssh->cscipher->setiv(ssh->cs_cipher_ctx, key); - smemclr(key, ssh->cscipher->blksize); - sfree(key); - } - if (ssh->csmac) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E', - ssh->csmac->keylen); - ssh->csmac->setkey(ssh->cs_mac_ctx, key); - smemclr(key, ssh->csmac->keylen); - sfree(key); - } - - if (ssh->cscipher) - logeventf(ssh, "Initialised %.200s client->server encryption", - ssh->cscipher->text_name); - if (ssh->csmac) - logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s", - ssh->csmac->text_name, - ssh->csmac_etm ? " (in ETM mode)" : "", - ssh->cscipher->required_mac ? " (required by cipher)" : ""); - if (ssh->cscomp->text_name) - logeventf(ssh, "Initialised %s compression", - ssh->cscomp->text_name); - - /* - * Now our end of the key exchange is complete, we can send all - * our queued higher-layer packets. - */ - ssh->queueing = FALSE; - ssh2_pkt_queuesend(ssh); - - /* - * Expect SSH2_MSG_NEWKEYS from server. - */ - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_NEWKEYS) { - bombout(("expected new-keys packet from server")); - crStopV; - } - ssh->incoming_data_size = 0; /* start counting from here */ - - /* - * We've seen server NEWKEYS, so create and initialise - * server-to-client session keys. - */ - if (ssh->sc_cipher_ctx) - ssh->sccipher->free_context(ssh->sc_cipher_ctx); - if (s->sccipher_tobe) { - ssh->sccipher = s->sccipher_tobe; - ssh->sc_cipher_ctx = ssh->sccipher->make_context(); - } - - if (ssh->sc_mac_ctx) - ssh->scmac->free_context(ssh->sc_mac_ctx); - if (s->scmac_tobe) { - ssh->scmac = s->scmac_tobe; - ssh->scmac_etm = s->scmac_etm_tobe; - ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx); - } - - if (ssh->sc_comp_ctx) - ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); - ssh->sccomp = s->sccomp_tobe; - ssh->sc_comp_ctx = ssh->sccomp->decompress_init(); - - /* - * Set IVs on server-to-client keys. Here we use the exchange - * hash from the _first_ key exchange. - */ - if (ssh->sccipher) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D', - ssh->sccipher->padded_keybytes); - ssh->sccipher->setkey(ssh->sc_cipher_ctx, key); - smemclr(key, ssh->sccipher->padded_keybytes); - sfree(key); - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'B', - ssh->sccipher->blksize); - ssh->sccipher->setiv(ssh->sc_cipher_ctx, key); - smemclr(key, ssh->sccipher->blksize); - sfree(key); - } - if (ssh->scmac) { - unsigned char *key; - - key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F', - ssh->scmac->keylen); - ssh->scmac->setkey(ssh->sc_mac_ctx, key); - smemclr(key, ssh->scmac->keylen); - sfree(key); - } - if (ssh->sccipher) - logeventf(ssh, "Initialised %.200s server->client encryption", - ssh->sccipher->text_name); - if (ssh->scmac) - logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s", - ssh->scmac->text_name, - ssh->scmac_etm ? " (in ETM mode)" : "", - ssh->sccipher->required_mac ? " (required by cipher)" : ""); - if (ssh->sccomp->text_name) - logeventf(ssh, "Initialised %s decompression", - ssh->sccomp->text_name); - - /* - * Free shared secret. - */ - freebn(s->K); - - /* - * Update the specials menu to list the remaining uncertified host - * keys. - */ - update_specials_menu(ssh->frontend); - - /* - * Key exchange is over. Loop straight back round if we have a - * deferred rekey reason. - */ - if (ssh->deferred_rekey_reason) { - logevent(ssh->deferred_rekey_reason); - pktin = NULL; - ssh->deferred_rekey_reason = NULL; - goto begin_key_exchange; - } - - /* - * Otherwise, schedule a timer for our next rekey. - */ - ssh->kex_in_progress = FALSE; - ssh->last_rekey = GETTICKCOUNT(); - if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) - ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC, - ssh2_timer, ssh); - - /* - * Now we're encrypting. Begin returning 1 to the protocol main - * function so that other things can run on top of the - * transport. If we ever see a KEXINIT, we must go back to the - * start. - * - * We _also_ go back to the start if we see pktin==NULL and - * inlen negative, because this is a special signal meaning - * `initiate client-driven rekey', and `in' contains a message - * giving the reason for the rekey. - * - * inlen==-1 means always initiate a rekey; - * inlen==-2 means that userauth has completed successfully and - * we should consider rekeying (for delayed compression). - */ - while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) || - (!pktin && inlen < 0))) { - wait_for_rekey: - if (!ssh->protocol_initial_phase_done) { - ssh->protocol_initial_phase_done = TRUE; - /* - * Allow authconn to initialise itself. - */ - do_ssh2_authconn(ssh, NULL, 0, NULL); - } - crReturnV; - } - if (pktin) { - logevent("Server initiated key re-exchange"); - } else { - if (inlen == -2) { - /* - * authconn has seen a USERAUTH_SUCCEEDED. Time to enable - * delayed compression, if it's available. - * - * draft-miller-secsh-compression-delayed-00 says that you - * negotiate delayed compression in the first key exchange, and - * both sides start compressing when the server has sent - * USERAUTH_SUCCESS. This has a race condition -- the server - * can't know when the client has seen it, and thus which incoming - * packets it should treat as compressed. - * - * Instead, we do the initial key exchange without offering the - * delayed methods, but note if the server offers them; when we - * get here, if a delayed method was available that was higher - * on our list than what we got, we initiate a rekey in which we - * _do_ list the delayed methods (and hopefully get it as a - * result). Subsequent rekeys will do the same. - */ - assert(!s->userauth_succeeded); /* should only happen once */ - s->userauth_succeeded = TRUE; - if (!s->pending_compression) - /* Can't see any point rekeying. */ - goto wait_for_rekey; /* this is utterly horrid */ - /* else fall through to rekey... */ - s->pending_compression = FALSE; - } - /* - * Now we've decided to rekey. - * - * Special case: if the server bug is set that doesn't - * allow rekeying, we give a different log message and - * continue waiting. (If such a server _initiates_ a rekey, - * we process it anyway!) - */ - if ((ssh->remote_bugs & BUG_SSH2_REKEY)) { - logeventf(ssh, "Server bug prevents key re-exchange (%s)", - (char *)in); - /* Reset the counters, so that at least this message doesn't - * hit the event log _too_ often. */ - ssh->outgoing_data_size = 0; - ssh->incoming_data_size = 0; - if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0) { - ssh->next_rekey = - schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC, - ssh2_timer, ssh); - } - goto wait_for_rekey; /* this is still utterly horrid */ - } else { - logeventf(ssh, "Initiating key re-exchange (%s)", (char *)in); - } - } - goto begin_key_exchange; - - crFinishV; -} - -/* - * Send data on an SSH channel. In SSH-2, this involves buffering it - * first. - */ -static int ssh_send_channel_data(struct ssh_channel *c, const char *buf, - int len) -{ - if (c->ssh->version == 2) { - bufchain_add(&c->v.v2.outbuffer, buf, len); - return ssh2_try_send(c); - } else { - send_packet(c->ssh, SSH1_MSG_CHANNEL_DATA, - PKT_INT, c->remoteid, - PKT_INT, len, - PKT_DATA, buf, len, - PKT_END); - /* - * 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; - } -} - -/* - * Attempt to send data on an SSH-2 channel. - */ -static int ssh2_try_send(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - struct Packet *pktout; - int ret; - - while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) { - int len; - void *data; - bufchain_prefix(&c->v.v2.outbuffer, &data, &len); - if ((unsigned)len > c->v.v2.remwindow) - len = c->v.v2.remwindow; - if ((unsigned)len > c->v.v2.remmaxpkt) - len = c->v.v2.remmaxpkt; - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_addstring_start(pktout); - ssh2_pkt_addstring_data(pktout, data, len); - ssh2_pkt_send(ssh, pktout); - bufchain_consume(&c->v.v2.outbuffer, len); - c->v.v2.remwindow -= len; - } - - /* - * After having sent as much data as we can, return the amount - * still buffered. - */ - ret = bufchain_size(&c->v.v2.outbuffer); - - /* - * And if there's no data pending but we need to send an EOF, send - * it. - */ - if (!ret && c->pending_eof) - ssh_channel_try_eof(c); - - return ret; -} - -static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c) -{ - int bufsize; - if (c->closes & CLOSES_SENT_EOF) - return; /* don't send on channels we've EOFed */ - bufsize = ssh2_try_send(c); - if (bufsize == 0) { - switch (c->type) { - case CHAN_MAINSESSION: - /* stdin need not receive an unthrottle - * notification since it will be polled */ - break; - case CHAN_X11: - x11_unthrottle(c->u.x11.xconn); - break; - case CHAN_AGENT: - /* Now that we've successfully sent all the outgoing - * replies we had, try to process more incoming data. */ - ssh_agentf_try_forward(c); - break; - case CHAN_SOCKDATA: - pfd_unthrottle(c->u.pfd.pf); - break; - } - } -} - -static int ssh_is_simple(Ssh ssh) -{ - /* - * 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. - */ - return (conf_get_int(ssh->conf, CONF_ssh_simple) && - !ssh->bare_connection && !ssh->connshare); -} - -/* - * Set up most of a new ssh_channel. - */ -static void ssh_channel_init(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - c->localid = alloc_channel_id(ssh); - c->closes = 0; - c->pending_eof = FALSE; - c->throttling_conn = FALSE; - if (ssh->version == 2) { - c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = - ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; - c->v.v2.chanreq_head = NULL; - c->v.v2.throttle_state = UNTHROTTLED; - bufchain_init(&c->v.v2.outbuffer); - } - add234(ssh->channels, c); -} - -/* - * Construct the common parts of a CHANNEL_OPEN. - */ -static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, - const char *type) -{ - struct Packet *pktout; - - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN); - ssh2_pkt_addstring(pktout, type); - ssh2_pkt_adduint32(pktout, c->localid); - ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */ - ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - return pktout; -} - -/* - * CHANNEL_FAILURE doesn't come with any indication of what message - * caused it, so we have to keep track of the outstanding - * CHANNEL_REQUESTs ourselves. - */ -static void ssh2_queue_chanreq_handler(struct ssh_channel *c, - cchandler_fn_t handler, void *ctx) -{ - struct outstanding_channel_request *ocr = - snew(struct outstanding_channel_request); - - assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - ocr->handler = handler; - ocr->ctx = ctx; - ocr->next = NULL; - if (!c->v.v2.chanreq_head) - c->v.v2.chanreq_head = ocr; - else - c->v.v2.chanreq_tail->next = ocr; - c->v.v2.chanreq_tail = ocr; -} - -/* - * Construct the common parts of a CHANNEL_REQUEST. If handler is not - * NULL then a reply will be requested and the handler will be called - * when it arrives. The returned packet is ready to have any - * request-specific data added and be sent. Note that if a handler is - * provided, it's essential that the request actually be sent. - * - * The handler will usually be passed the response packet in pktin. If - * pktin is NULL, this means that no reply will ever be forthcoming - * (e.g. because the entire connection is being destroyed, or because - * the server initiated channel closure before we saw the response) - * and the handler should free any storage it's holding. - */ -static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, - const char *type, - cchandler_fn_t handler, void *ctx) -{ - struct Packet *pktout; - - assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_addstring(pktout, type); - ssh2_pkt_addbool(pktout, handler != NULL); - if (handler != NULL) - ssh2_queue_chanreq_handler(c, handler, ctx); - return pktout; -} - -static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize) -{ - Ssh ssh = c->ssh; - int buflimit; - - if (ssh->version == 1) { - buflimit = SSH1_BUFFER_LIMIT; - } else { - if (ssh_is_simple(ssh)) - buflimit = 0; - else - buflimit = c->v.v2.locmaxwin; - if (bufsize < buflimit) - ssh2_set_window(c, buflimit - bufsize); - } - if (c->throttling_conn && bufsize <= buflimit) { - c->throttling_conn = 0; - ssh_throttle_conn(ssh, -1); - } -} - -/* - * Potentially enlarge the window on an SSH-2 channel. - */ -static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *, - void *); -static void ssh2_set_window(struct ssh_channel *c, int newwin) -{ - Ssh ssh = c->ssh; - - /* - * Never send WINDOW_ADJUST for a channel that the remote side 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; - - /* - * Also, never widen the window for an X11 channel when we're - * still waiting to see its initial auth and may yet hand it off - * to a downstream. - */ - if (c->type == CHAN_X11 && c->u.x11.initial) - return; - - /* - * If the remote end has a habit of ignoring maxpkt, limit the - * window so that it has no choice (assuming it doesn't ignore the - * window as well). - */ - if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT) - newwin = OUR_V2_MAXPKT; - - /* - * Only send a WINDOW_ADJUST if there's significantly more window - * available than the other end thinks there is. This saves us - * sending a WINDOW_ADJUST for every character in a shell session. - * - * "Significant" is arbitrarily defined as half the window size. - */ - if (newwin / 2 >= c->v.v2.locwindow) { - struct Packet *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->v.v2.locmaxwin && - !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) { - up = snew(unsigned); - *up = newwin - c->v.v2.locwindow; - pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", - ssh2_handle_winadj_response, up); - ssh2_pkt_send(ssh, pktout); - - if (c->v.v2.throttle_state != UNTHROTTLED) - c->v.v2.throttle_state = UNTHROTTLING; - } else { - /* Pretend the WINDOW_ADJUST was acked immediately. */ - c->v.v2.remlocwin = newwin; - c->v.v2.throttle_state = THROTTLED; - } - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow); - ssh2_pkt_send(ssh, pktout); - c->v.v2.locwindow = newwin; - } -} - -/* - * Find the channel associated with a message. If there's no channel, - * or it's not properly open, make a noise about it and return NULL. - * If the channel is shared, pass the message on to downstream and - * also return NULL (meaning the caller should ignore this message). - */ -static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin) -{ - unsigned localid = ssh_pkt_getuint32(pktin); - struct ssh_channel *c; - int halfopen_ok; - - /* Is this message OK on a half-open connection? */ - if (ssh->version == 1) - halfopen_ok = (pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE); - else - halfopen_ok = (pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); - c = find234(ssh->channels, &localid, ssh_channelfind); - if (!c || (c->type != CHAN_SHARING && (c->halfopen != halfopen_ok))) { - char *buf = dupprintf("Received %s for %s channel %u", - ssh_pkt_type(ssh, pktin->type), - !c ? "nonexistent" : - c->halfopen ? "half-open" : "open", - localid); - ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); - sfree(buf); - return NULL; - } - if (c->type == CHAN_SHARING) { - share_got_pkt_from_server(c->u.sharing.ctx, pktin->type, - pktin->body, pktin->length); - return NULL; - } - return c; -} - -static void ssh2_handle_winadj_response(struct ssh_channel *c, - struct Packet *pktin, void *ctx) -{ - unsigned *sizep = ctx; - - /* - * Winadj responses should always be failures. However, at least - * one server ("boks_sshd") is known to return SUCCESS for channel - * requests it's never heard of, such as "winadj@putty". Raised - * with foxt.com as bug 090916-090424, but for the sake of a quiet - * life, we don't worry about what kind of response we got. - */ - - c->v.v2.remlocwin += *sizep; - sfree(sizep); - /* - * winadj messages are only sent when the window is fully open, so - * if we get an ack of one, we know any pending unthrottle is - * complete. - */ - if (c->v.v2.throttle_state == UNTHROTTLING) - c->v.v2.throttle_state = UNTHROTTLED; -} - -static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c = ssh_channel_msg(ssh, pktin); - struct outstanding_channel_request *ocr; - - if (!c) return; - ocr = c->v.v2.chanreq_head; - if (!ocr) { - ssh2_msg_unexpected(ssh, pktin); - return; - } - ocr->handler(c, pktin, ocr->ctx); - c->v.v2.chanreq_head = ocr->next; - sfree(ocr); - /* - * We may now initiate channel-closing procedures, if that - * CHANNEL_REQUEST was the last thing outstanding before we send - * CHANNEL_CLOSE. - */ - ssh2_channel_check_close(c); -} - -static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c; - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - if (!(c->closes & CLOSES_SENT_EOF)) { - c->v.v2.remwindow += ssh_pkt_getuint32(pktin); - ssh2_try_send_and_unthrottle(ssh, c); - } -} - -static void ssh2_msg_channel_data(Ssh ssh, struct Packet *pktin) -{ - char *data; - int length; - unsigned ext_type = 0; /* 0 means not extended */ - struct ssh_channel *c; - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - ext_type = ssh_pkt_getuint32(pktin); - ssh_pkt_getstring(pktin, &data, &length); - if (data) { - int bufsize; - c->v.v2.locwindow -= length; - c->v.v2.remlocwin -= length; - if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR) - length = 0; /* Don't do anything with unknown extended data. */ - bufsize = ssh_channel_data(c, ext_type == SSH2_EXTENDED_DATA_STDERR, - data, length); - /* - * If it looks like the remote end hit the end of its window, - * and we didn't want it to do that, think about using a - * larger window. - */ - if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED && - c->v.v2.locmaxwin < 0x40000000) - c->v.v2.locmaxwin += OUR_V2_WINSIZE; - /* - * If we are not buffering too much data, - * enlarge the window again at the remote side. - * If we are buffering too much, we may still - * need to adjust the window if the server's - * sent excess data. - */ - if (bufsize < c->v.v2.locmaxwin) - ssh2_set_window(c, c->v.v2.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->v.v2.locmaxwin || (ssh_is_simple(ssh) && bufsize>0)) - && !c->throttling_conn) { - c->throttling_conn = 1; - ssh_throttle_conn(ssh, +1); - } - } -} - -static void ssh_check_termination(Ssh ssh) -{ - if (ssh->version == 2 && - !conf_get_int(ssh->conf, CONF_ssh_no_shell) && - (ssh->channels && count234(ssh->channels) == 0) && - !(ssh->connshare && share_ndownstreams(ssh->connshare) > 0)) { - /* - * We used to send SSH_MSG_DISCONNECT here, because I'd - * believed that _every_ conforming SSH-2 connection had to - * end with a disconnect being sent by at least one side; - * apparently I was wrong and it's perfectly OK to - * unceremoniously slam the connection shut when you're done, - * and indeed OpenSSH feels this is more polite than sending a - * DISCONNECT. So now we don't. - */ - ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE); - } -} - -void ssh_sharing_downstream_connected(Ssh ssh, unsigned id, - const char *peerinfo) -{ - if (peerinfo) - logeventf(ssh, "Connection sharing downstream #%u connected from %s", - id, peerinfo); - else - logeventf(ssh, "Connection sharing downstream #%u connected", id); -} - -void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id) -{ - logeventf(ssh, "Connection sharing downstream #%u disconnected", id); - ssh_check_termination(ssh); -} - -void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - if (id) - logeventf(ssh, "Connection sharing downstream #%u: %s", id, buf); - else - logeventf(ssh, "Connection sharing: %s", buf); - sfree(buf); -} - -/* - * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a CHAN_ZOMBIE. - */ -static void ssh_channel_close_local(struct ssh_channel *c, char const *reason) -{ - Ssh ssh = c->ssh; - char const *msg = NULL; - - switch (c->type) { - case CHAN_MAINSESSION: - ssh->mainchan = NULL; - update_specials_menu(ssh->frontend); - break; - case CHAN_X11: - assert(c->u.x11.xconn != NULL); - x11_close(c->u.x11.xconn); - msg = "Forwarded X11 connection terminated"; - break; - case CHAN_AGENT: - if (c->u.a.pending) - agent_cancel_query(c->u.a.pending); - bufchain_clear(&c->u.a.inbuffer); - msg = "Agent-forwarding connection closed"; - break; - case CHAN_SOCKDATA: - assert(c->u.pfd.pf != NULL); - pfd_close(c->u.pfd.pf); - msg = "Forwarded port closed"; - break; - } - c->type = CHAN_ZOMBIE; - if (msg != NULL) { - if (reason != NULL) - logeventf(ssh, "%s %s", msg, reason); - else - logevent(msg); - } -} - -static void ssh_channel_destroy(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - - ssh_channel_close_local(c, NULL); - - del234(ssh->channels, c); - if (ssh->version == 2) { - bufchain_clear(&c->v.v2.outbuffer); - assert(c->v.v2.chanreq_head == NULL); - } - sfree(c); - - /* - * If that was the last channel left open, we might need to - * terminate. - */ - ssh_check_termination(ssh); -} - -static void ssh2_channel_check_close(struct ssh_channel *c) -{ - Ssh ssh = c->ssh; - struct Packet *pktout; - - assert(ssh->version == 2); - if (c->halfopen) { - /* - * If we've sent out our own CHANNEL_OPEN but not yet seen - * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then - * it's too early to be sending close messages of any kind. - */ - return; - } - - if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) || - c->type == CHAN_ZOMBIE) && - !c->v.v2.chanreq_head && - !(c->closes & CLOSES_SENT_CLOSE)) { - /* - * We have both sent and received EOF (or the channel is a - * zombie), and we have no outstanding channel requests, which - * means the channel is in final wind-up. But we haven't sent - * CLOSE, so let's do so now. - */ - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; - } - - if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { - assert(c->v.v2.chanreq_head == NULL); - /* - * We have both sent and received CLOSE, which means we're - * completely done with the channel. - */ - ssh_channel_destroy(c); - } -} - -static void ssh_channel_got_eof(struct ssh_channel *c) -{ - if (c->closes & CLOSES_RCVD_EOF) - return; /* already seen EOF */ - c->closes |= CLOSES_RCVD_EOF; - - if (c->type == CHAN_X11) { - assert(c->u.x11.xconn != NULL); - x11_send_eof(c->u.x11.xconn); - } else if (c->type == CHAN_AGENT) { - /* Just 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 */ - ssh_agentf_try_forward(c); - } else if (c->type == CHAN_SOCKDATA) { - assert(c->u.pfd.pf != NULL); - pfd_send_eof(c->u.pfd.pf); - } else if (c->type == CHAN_MAINSESSION) { - Ssh ssh = c->ssh; - - if (!ssh->sent_console_eof && - (from_backend_eof(ssh->frontend) || ssh->got_pty)) { - /* - * Either from_backend_eof told us that the front end - * wants us to close the outgoing side of the connection - * as soon as we see EOF from the far end, or else we've - * unilaterally decided to do that because we've allocated - * a remote pty and hence EOF isn't a particularly - * meaningful concept. - */ - sshfwd_write_eof(c); - } - ssh->sent_console_eof = TRUE; - } -} - -static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - ssh_channel_got_eof(c); - ssh2_channel_check_close(c); -} - -static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - - /* - * When we receive CLOSE on a channel, we assume it comes with an - * implied EOF if we haven't seen EOF yet. - */ - ssh_channel_got_eof(c); - - if (!(ssh->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->v.v2.chanreq_head) { - struct outstanding_channel_request *ocr = c->v.v2.chanreq_head; - ocr->handler(c, NULL, ocr->ctx); - c->v.v2.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)) { - /* - * Make sure we don't read any more from whatever our local - * data source is for this channel. - */ - switch (c->type) { - case CHAN_MAINSESSION: - ssh->send_ok = 0; /* stop trying to read from stdin */ - break; - case CHAN_X11: - x11_override_throttle(c->u.x11.xconn, 1); - break; - case CHAN_SOCKDATA: - pfd_override_throttle(c->u.pfd.pf, 1); - break; - } - - /* - * Abandon any buffered data we still wanted to send to this - * channel. Receiving a CHANNEL_CLOSE is an indication that - * the server really wants to get on and _destroy_ this - * channel, and it isn't going to send us any further - * WINDOW_ADJUSTs to permit us to send pending stuff. - */ - bufchain_clear(&c->v.v2.outbuffer); - - /* - * Send outgoing EOF. - */ - sshfwd_write_eof(c); - } - - /* - * Now process the actual close. - */ - if (!(c->closes & CLOSES_RCVD_CLOSE)) { - c->closes |= CLOSES_RCVD_CLOSE; - ssh2_channel_check_close(c); - } -} - -static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin) -{ - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - assert(c->halfopen); /* ssh_channel_msg will have enforced this */ - c->remoteid = ssh_pkt_getuint32(pktin); - c->halfopen = FALSE; - c->v.v2.remwindow = ssh_pkt_getuint32(pktin); - c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); - - if (c->type == CHAN_SOCKDATA) { - assert(c->u.pfd.pf != NULL); - pfd_confirm(c->u.pfd.pf); - } else if (c->type == CHAN_ZOMBIE) { - /* - * This case can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_CONFIRMATION. In this case, all we can do is - * immediately initiate close proceedings now that we know the - * server's id to put in the close message. - */ - ssh2_channel_check_close(c); - } else { - /* - * We never expect to receive OPEN_CONFIRMATION for any - * *other* channel type (since only local-to-remote port - * forwardings cause us to send CHANNEL_OPEN after the main - * channel is live - all other auxiliary channel types are - * initiated from the server end). It's safe to enforce this - * by assertion rather than by ssh_disconnect, because the - * real point is that we never constructed a half-open channel - * structure in the first place with any type other than the - * above. - */ - assert(!"Funny channel type in ssh2_msg_channel_open_confirmation"); - } - - if (c->pending_eof) - ssh_channel_try_eof(c); /* in case we had a pending EOF */ -} - -static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) -{ - static const char *const reasons[] = { - "", - "Administratively prohibited", - "Connect failed", - "Unknown channel type", - "Resource shortage", - }; - unsigned reason_code; - char *reason_string; - int reason_length; - struct ssh_channel *c; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - assert(c->halfopen); /* ssh_channel_msg will have enforced this */ - - if (c->type == CHAN_SOCKDATA) { - reason_code = ssh_pkt_getuint32(pktin); - if (reason_code >= lenof(reasons)) - reason_code = 0; /* ensure reasons[reason_code] in range */ - ssh_pkt_getstring(pktin, &reason_string, &reason_length); - logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", - reasons[reason_code], reason_length, - NULLTOEMPTY(reason_string)); - - pfd_close(c->u.pfd.pf); - } else if (c->type == CHAN_ZOMBIE) { - /* - * This case can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_FAILURE. In this case, we need do nothing except allow - * the code below to throw the half-open channel away. - */ - } else { - /* - * We never expect to receive OPEN_FAILURE for any *other* - * channel type (since only local-to-remote port forwardings - * cause us to send CHANNEL_OPEN after the main channel is - * live - all other auxiliary channel types are initiated from - * the server end). It's safe to enforce this by assertion - * rather than by ssh_disconnect, because the real point is - * that we never constructed a half-open channel structure in - * the first place with any type other than the above. - */ - assert(!"Funny channel type in ssh2_msg_channel_open_failure"); - } - - del234(ssh->channels, c); - sfree(c); -} - -static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin) -{ - char *type; - int typelen, want_reply; - int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */ - struct ssh_channel *c; - struct Packet *pktout; - - c = ssh_channel_msg(ssh, pktin); - if (!c) - return; - ssh_pkt_getstring(pktin, &type, &typelen); - want_reply = ssh2_pkt_getbool(pktin); - - 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; - } - - /* - * Having got the channel number, we now look at - * the request type string to see if it's something - * we recognise. - */ - if (c == ssh->mainchan) { - /* - * We recognise "exit-status" and "exit-signal" on - * the primary channel. - */ - if (typelen == 11 && - !memcmp(type, "exit-status", 11)) { - - ssh->exitcode = ssh_pkt_getuint32(pktin); - logeventf(ssh, "Server sent command exit status %d", - ssh->exitcode); - reply = SSH2_MSG_CHANNEL_SUCCESS; - - } else if (typelen == 11 && - !memcmp(type, "exit-signal", 11)) { - - int is_plausible = TRUE, is_int = FALSE; - char *fmt_sig = NULL, *fmt_msg = NULL; - char *msg; - int msglen = 0, core = FALSE; - /* ICK: older versions of OpenSSH (e.g. 3.4p1) - * provide an `int' for the signal, despite its - * having been a `string' in the drafts of RFC 4254 since at - * least 2001. (Fixed in session.c 1.147.) Try to - * infer which we can safely parse it as. */ - { - unsigned char *p = pktin->body + - pktin->savedpos; - long len = pktin->length - pktin->savedpos; - unsigned long num = GET_32BIT(p); /* what is it? */ - /* If it's 0, it hardly matters; assume string */ - if (num == 0) { - is_int = FALSE; - } else { - int maybe_int = FALSE, maybe_str = FALSE; -#define CHECK_HYPOTHESIS(offset, result) \ - do \ - { \ - int q = toint(offset); \ - if (q >= 0 && q+4 <= len) { \ - q = toint(q + 4 + GET_32BIT(p+q)); \ - if (q >= 0 && q+4 <= len && \ - ((q = toint(q + 4 + GET_32BIT(p+q))) != 0) && \ - q == len) \ - result = TRUE; \ - } \ - } while(0) - CHECK_HYPOTHESIS(4+1, maybe_int); - CHECK_HYPOTHESIS(4+num+1, maybe_str); -#undef CHECK_HYPOTHESIS - if (maybe_int && !maybe_str) - is_int = TRUE; - else if (!maybe_int && maybe_str) - is_int = FALSE; - else - /* Crikey. Either or neither. Panic. */ - is_plausible = FALSE; - } - } - ssh->exitcode = 128; /* means `unknown signal' */ - if (is_plausible) { - if (is_int) { - /* Old non-standard OpenSSH. */ - int signum = ssh_pkt_getuint32(pktin); - fmt_sig = dupprintf(" %d", signum); - ssh->exitcode = 128 + signum; - } else { - /* As per RFC 4254. */ - char *sig; - int siglen; - ssh_pkt_getstring(pktin, &sig, &siglen); - /* Signal name isn't supposed to be blank, but - * let's cope gracefully if it is. */ - if (siglen) { - fmt_sig = dupprintf(" \"%.*s\"", - siglen, sig); - } - - /* - * Really hideous method of translating the - * signal description back into a locally - * meaningful number. - */ - - if (0) - ; -#define TRANSLATE_SIGNAL(s) \ - else if (siglen == lenof(#s)-1 && !memcmp(sig, #s, siglen)) \ - ssh->exitcode = 128 + SIG ## s -#ifdef SIGABRT - TRANSLATE_SIGNAL(ABRT); -#endif -#ifdef SIGALRM - TRANSLATE_SIGNAL(ALRM); -#endif -#ifdef SIGFPE - TRANSLATE_SIGNAL(FPE); -#endif -#ifdef SIGHUP - TRANSLATE_SIGNAL(HUP); -#endif -#ifdef SIGILL - TRANSLATE_SIGNAL(ILL); -#endif -#ifdef SIGINT - TRANSLATE_SIGNAL(INT); -#endif -#ifdef SIGKILL - TRANSLATE_SIGNAL(KILL); -#endif -#ifdef SIGPIPE - TRANSLATE_SIGNAL(PIPE); -#endif -#ifdef SIGQUIT - TRANSLATE_SIGNAL(QUIT); -#endif -#ifdef SIGSEGV - TRANSLATE_SIGNAL(SEGV); -#endif -#ifdef SIGTERM - TRANSLATE_SIGNAL(TERM); -#endif -#ifdef SIGUSR1 - TRANSLATE_SIGNAL(USR1); -#endif -#ifdef SIGUSR2 - TRANSLATE_SIGNAL(USR2); -#endif -#undef TRANSLATE_SIGNAL - else - ssh->exitcode = 128; - } - core = ssh2_pkt_getbool(pktin); - ssh_pkt_getstring(pktin, &msg, &msglen); - if (msglen) { - fmt_msg = dupprintf(" (\"%.*s\")", msglen, msg); - } - /* ignore lang tag */ - } /* else don't attempt to parse */ - logeventf(ssh, "Server exited on signal%s%s%s", - fmt_sig ? fmt_sig : "", - core ? " (core dumped)" : "", - fmt_msg ? fmt_msg : ""); - sfree(fmt_sig); - sfree(fmt_msg); - reply = SSH2_MSG_CHANNEL_SUCCESS; - - } - } else { - /* - * This is a channel request we don't know - * about, so we now either ignore the request - * or respond with CHANNEL_FAILURE, depending - * on want_reply. - */ - reply = SSH2_MSG_CHANNEL_FAILURE; - } - if (want_reply) { - pktout = ssh2_pkt_init(reply); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_send(ssh, pktout); - } -} - -static void ssh2_msg_global_request(Ssh ssh, struct Packet *pktin) -{ - char *type; - int typelen, want_reply; - struct Packet *pktout; - - ssh_pkt_getstring(pktin, &type, &typelen); - want_reply = ssh2_pkt_getbool(pktin); - - /* - * We currently don't support any global requests - * at all, so we either ignore the request or - * respond with REQUEST_FAILURE, depending on - * want_reply. - */ - if (want_reply) { - pktout = ssh2_pkt_init(SSH2_MSG_REQUEST_FAILURE); - ssh2_pkt_send(ssh, pktout); - } -} - -struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype, - void *share_cs, - void *share_chan) -{ - struct X11FakeAuth *auth; - - /* - * Make up a new set of fake X11 auth data, and add it to the tree - * of currently valid ones with an indication of the sharing - * context that it's relevant to. - */ - auth = x11_invent_fake_auth(ssh->x11authtree, authtype); - auth->share_cs = share_cs; - auth->share_chan = share_chan; - - return auth; -} - -void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth) -{ - del234(ssh->x11authtree, auth); - x11_free_fake_auth(auth); -} - -static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) -{ - char *type; - int typelen; - char *peeraddr; - int peeraddrlen; - int peerport; - const char *error = NULL; - struct ssh_channel *c; - unsigned remid, winsize, pktsize; - unsigned our_winsize_override = 0; - struct Packet *pktout; - - ssh_pkt_getstring(pktin, &type, &typelen); - c = snew(struct ssh_channel); - c->ssh = ssh; - - remid = ssh_pkt_getuint32(pktin); - winsize = ssh_pkt_getuint32(pktin); - pktsize = ssh_pkt_getuint32(pktin); - - if (typelen == 3 && !memcmp(type, "x11", 3)) { - char *addrstr; - - ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); - addrstr = dupprintf("%.*s", peeraddrlen, NULLTOEMPTY(peeraddr)); - peerport = ssh_pkt_getuint32(pktin); - - logeventf(ssh, "Received X11 connect request from %s:%d", - addrstr, peerport); - - if (!ssh->X11_fwd_enabled && !ssh->connshare) - error = "X11 forwarding is not enabled"; - else { - c->u.x11.xconn = x11_init(ssh->x11authtree, c, - addrstr, peerport); - c->type = CHAN_X11; - c->u.x11.initial = TRUE; - - /* - * If we are a connection-sharing upstream, then we should - * initially present a very small window, adequate to take - * the X11 initial authorisation packet but not much more. - * Downstream will then present us a larger window (by - * fiat of the connection-sharing protocol) and we can - * guarantee to send a positive-valued WINDOW_ADJUST. - */ - if (ssh->connshare) - our_winsize_override = 128; - - logevent("Opened X11 forward channel"); - } - - sfree(addrstr); - } else if (typelen == 15 && - !memcmp(type, "forwarded-tcpip", 15)) { - struct ssh_rportfwd pf, *realpf; - char *shost; - int shostlen; - ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */ - pf.shost = dupprintf("%.*s", shostlen, NULLTOEMPTY(shost)); - pf.sport = ssh_pkt_getuint32(pktin); - ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); - peerport = ssh_pkt_getuint32(pktin); - realpf = find234(ssh->rportfwds, &pf, NULL); - logeventf(ssh, "Received remote port %s:%d open request " - "from %.*s:%d", pf.shost, pf.sport, - peeraddrlen, NULLTOEMPTY(peeraddr), peerport); - sfree(pf.shost); - - if (realpf == NULL) { - error = "Remote port is not recognised"; - } else { - char *err; - - if (realpf->share_ctx) { - /* - * This port forwarding is on behalf of a - * connection-sharing downstream, so abandon our own - * channel-open procedure and just pass the message on - * to sshshare.c. - */ - share_got_pkt_from_server(realpf->share_ctx, pktin->type, - pktin->body, pktin->length); - sfree(c); - return; - } - - err = pfd_connect(&c->u.pfd.pf, realpf->dhost, realpf->dport, - c, ssh->conf, realpf->pfrec->addressfamily); - logeventf(ssh, "Attempting to forward remote port to " - "%s:%d", realpf->dhost, realpf->dport); - if (err != NULL) { - logeventf(ssh, "Port open failed: %s", err); - sfree(err); - error = "Port open failed"; - } else { - logevent("Forwarded port opened successfully"); - c->type = CHAN_SOCKDATA; - } - } - } else if (typelen == 22 && - !memcmp(type, "auth-agent@openssh.com", 22)) { - if (!ssh->agentfwd_enabled) - error = "Agent forwarding is not enabled"; - else { - c->type = CHAN_AGENT; /* identify channel type */ - bufchain_init(&c->u.a.inbuffer); - c->u.a.pending = NULL; - } - } else { - error = "Unsupported channel type requested"; - } - - c->remoteid = remid; - c->halfopen = FALSE; - if (error) { - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_FAILURE); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_adduint32(pktout, SSH2_OPEN_CONNECT_FAILED); - ssh2_pkt_addstring(pktout, error); - ssh2_pkt_addstring(pktout, "en"); /* language tag */ - ssh2_pkt_send(ssh, pktout); - logeventf(ssh, "Rejected channel open: %s", error); - sfree(c); - } else { - ssh_channel_init(c); - c->v.v2.remwindow = winsize; - c->v.v2.remmaxpkt = pktsize; - if (our_winsize_override) { - c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin = - our_winsize_override; - } - pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); - ssh2_pkt_adduint32(pktout, c->remoteid); - ssh2_pkt_adduint32(pktout, c->localid); - ssh2_pkt_adduint32(pktout, c->v.v2.locwindow); - ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - ssh2_pkt_send(ssh, pktout); - } -} - -void sshfwd_x11_sharing_handover(struct ssh_channel *c, - void *share_cs, void *share_chan, - const char *peer_addr, int peer_port, - int endian, int protomajor, int protominor, - const void *initial_data, int initial_len) -{ - /* - * This function is called when we've just discovered that an X - * forwarding channel on which we'd been handling the initial auth - * ourselves turns out to be destined for a connection-sharing - * downstream. So we turn the channel into a CHAN_SHARING, meaning - * that we completely stop tracking windows and buffering data and - * just pass more or less unmodified SSH messages back and forth. - */ - c->type = CHAN_SHARING; - c->u.sharing.ctx = share_cs; - share_setup_x11_channel(share_cs, share_chan, - c->localid, c->remoteid, c->v.v2.remwindow, - c->v.v2.remmaxpkt, c->v.v2.locwindow, - peer_addr, peer_port, endian, - protomajor, protominor, - initial_data, initial_len); -} - -void sshfwd_x11_is_local(struct ssh_channel *c) -{ - /* - * This function is called when we've just discovered that an X - * forwarding channel is _not_ destined for a connection-sharing - * downstream but we're going to handle it ourselves. We stop - * presenting a cautiously small window and go into ordinary data - * exchange mode. - */ - c->u.x11.initial = FALSE; - ssh2_set_window(c, ssh_is_simple(c->ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); -} - -/* - * Buffer banner messages for later display at some convenient point, - * if we're going to display them. - */ -static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin) -{ - /* Arbitrary limit to prevent unbounded inflation of buffer */ - if (conf_get_int(ssh->conf, CONF_ssh_show_banner) && - bufchain_size(&ssh->banner) <= 131072) { - char *banner = NULL; - int size = 0; - ssh_pkt_getstring(pktin, &banner, &size); - if (banner) - bufchain_add(&ssh->banner, banner, size); - } -} - -/* Helper function to deal with sending tty modes for "pty-req" */ -static void ssh2_send_ttymode(void *data, - const struct ssh_ttymode *mode, char *val) -{ - struct Packet *pktout = (struct Packet *)data; - unsigned int arg = 0; - - switch (mode->type) { - case TTY_OP_CHAR: - arg = ssh_tty_parse_specchar(val); - break; - case TTY_OP_BOOL: - arg = ssh_tty_parse_boolean(val); - break; - } - ssh2_pkt_addbyte(pktout, mode->opcode); - ssh2_pkt_adduint32(pktout, arg); -} - -static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin, - void *ctx) -{ - struct ssh2_setup_x11_state { - int crLine; - }; - Ssh ssh = c->ssh; - struct Packet *pktout; - crStateP(ssh2_setup_x11_state, ctx); - - crBeginState; - - logevent("Requesting X11 forwarding"); - pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req", - ssh2_setup_x11, s); - ssh2_pkt_addbool(pktout, 0); /* many connections */ - ssh2_pkt_addstring(pktout, ssh->x11auth->protoname); - ssh2_pkt_addstring(pktout, ssh->x11auth->datastring); - ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum); - ssh2_pkt_send(ssh, pktout); - - /* Wait to be called back with either a response packet, or NULL - * meaning clean up and free our data */ - crReturnV; - - if (pktin) { - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { - logevent("X11 forwarding enabled"); - ssh->X11_fwd_enabled = TRUE; - } else - logevent("X11 forwarding refused"); - } - - crFinishFreeV; -} - -static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin, - void *ctx) -{ - struct ssh2_setup_agent_state { - int crLine; - }; - Ssh ssh = c->ssh; - struct Packet *pktout; - crStateP(ssh2_setup_agent_state, ctx); - - crBeginState; - - logevent("Requesting OpenSSH-style agent forwarding"); - pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com", - ssh2_setup_agent, s); - ssh2_pkt_send(ssh, pktout); - - /* Wait to be called back with either a response packet, or NULL - * meaning clean up and free our data */ - crReturnV; - - if (pktin) { - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { - logevent("Agent forwarding enabled"); - ssh->agentfwd_enabled = TRUE; - } else - logevent("Agent forwarding refused"); - } - - crFinishFreeV; -} - -static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin, - void *ctx) -{ - struct ssh2_setup_pty_state { - int crLine; - }; - Ssh ssh = c->ssh; - struct Packet *pktout; - crStateP(ssh2_setup_pty_state, ctx); - - crBeginState; - - /* Unpick the terminal-speed string. */ - /* XXX perhaps we should allow no speeds to be sent. */ - ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed); - /* Build the pty request. */ - pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req", - ssh2_setup_pty, s); - ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype)); - ssh2_pkt_adduint32(pktout, ssh->term_width); - ssh2_pkt_adduint32(pktout, ssh->term_height); - ssh2_pkt_adduint32(pktout, 0); /* pixel width */ - ssh2_pkt_adduint32(pktout, 0); /* pixel height */ - ssh2_pkt_addstring_start(pktout); - parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout); - ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED); - ssh2_pkt_adduint32(pktout, ssh->ispeed); - ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED); - ssh2_pkt_adduint32(pktout, ssh->ospeed); - ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */ - ssh2_pkt_send(ssh, pktout); - ssh->state = SSH_STATE_INTERMED; - - /* Wait to be called back with either a response packet, or NULL - * meaning clean up and free our data */ - crReturnV; - - if (pktin) { - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) { - logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)", - ssh->ospeed, ssh->ispeed); - ssh->got_pty = TRUE; - } else { - c_write_str(ssh, "Server refused to allocate pty\r\n"); - ssh->editing = ssh->echoing = 1; - } - } - - crFinishFreeV; -} - -static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin, - void *ctx) -{ - struct ssh2_setup_env_state { - int crLine; - int num_env, env_left, env_ok; - }; - Ssh ssh = c->ssh; - struct Packet *pktout; - crStateP(ssh2_setup_env_state, ctx); - - crBeginState; - - /* - * Send environment variables. - * - * Simplest thing here is to send all the requests at once, and - * then wait for a whole bunch of successes or failures. - */ - s->num_env = 0; - { - char *key, *val; - - for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key); - val != NULL; - val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) { - pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s); - ssh2_pkt_addstring(pktout, key); - ssh2_pkt_addstring(pktout, val); - ssh2_pkt_send(ssh, pktout); - - s->num_env++; - } - if (s->num_env) - logeventf(ssh, "Sent %d environment variables", s->num_env); - } - - if (s->num_env) { - s->env_ok = 0; - s->env_left = s->num_env; - - while (s->env_left > 0) { - /* Wait to be called back with either a response packet, - * or NULL meaning clean up and free our data */ - crReturnV; - if (!pktin) goto out; - if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) - s->env_ok++; - s->env_left--; - } - - if (s->env_ok == s->num_env) { - logevent("All environment variables successfully set"); - } else if (s->env_ok == 0) { - logevent("All environment variables refused"); - c_write_str(ssh, "Server refused to set environment variables\r\n"); - } else { - logeventf(ssh, "%d environment variables refused", - s->num_env - s->env_ok); - c_write_str(ssh, "Server refused to set all environment variables\r\n"); - } - } - out:; - crFinishFreeV; -} - -/* - * Handle the SSH-2 userauth and connection layers. - */ -static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin) -{ - do_ssh2_authconn(ssh, NULL, 0, pktin); -} - -static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin, - void *ctx) -{ - if (pktin) - do_ssh2_authconn(c->ssh, NULL, 0, pktin); -} - -static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, - struct Packet *pktin) -{ - struct do_ssh2_authconn_state { - int crLine; - enum { - AUTH_TYPE_NONE, - AUTH_TYPE_PUBLICKEY, - AUTH_TYPE_PUBLICKEY_OFFER_LOUD, - AUTH_TYPE_PUBLICKEY_OFFER_QUIET, - AUTH_TYPE_PASSWORD, - AUTH_TYPE_GSSAPI, /* always QUIET */ - AUTH_TYPE_KEYBOARD_INTERACTIVE, - AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET - } type; - int done_service_req; - int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter; - int tried_pubkey_config, done_agent; -#ifndef NO_GSSAPI - int can_gssapi; - int tried_gssapi; -#endif - int kbd_inter_refused; - int we_are_in, userauth_success; - prompts_t *cur_prompt; - int num_prompts; - char *username; - char *password; - int got_username; - void *publickey_blob; - int publickey_bloblen; - int privatekey_available, privatekey_encrypted; - char *publickey_algorithm; - char *publickey_comment; - unsigned char agent_request[5], *agent_response, *agentp; - int agent_responselen; - unsigned char *pkblob_in_agent; - int keyi, nkeys; - char *pkblob, *alg, *commentp; - int pklen, alglen, commentlen; - int siglen, retlen, len; - char *q, *agentreq, *ret; - struct Packet *pktout; - Filename *keyfile; -#ifndef NO_GSSAPI - struct ssh_gss_library *gsslib; - Ssh_gss_ctx gss_ctx; - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_name gss_srv_name; - Ssh_gss_stat gss_stat; -#endif - }; - crState(do_ssh2_authconn_state); - - crBeginState; - - /* Register as a handler for all the messages this coroutine handles. */ - ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_authconn; - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_authconn; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_authconn; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn; - - s->done_service_req = FALSE; - s->we_are_in = s->userauth_success = FALSE; - s->agent_response = NULL; -#ifndef NO_GSSAPI - s->tried_gssapi = FALSE; -#endif - - if (!ssh->bare_connection) { - if (!conf_get_int(ssh->conf, CONF_ssh_no_userauth)) { - /* - * Request userauth protocol, and await a response to it. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); - ssh2_pkt_addstring(s->pktout, "ssh-userauth"); - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) - s->done_service_req = TRUE; - } - if (!s->done_service_req) { - /* - * Request connection protocol directly, without authentication. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) { - s->we_are_in = TRUE; /* no auth required */ - } else { - bombout(("Server refused service request")); - crStopV; - } - } - } else { - s->we_are_in = TRUE; - } - - /* Arrange to be able to deal with any BANNERs that come in. - * (We do this now as packets may come in during the next bit.) */ - bufchain_init(&ssh->banner); - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = - ssh2_msg_userauth_banner; - - /* - * Misc one-time setup for authentication. - */ - s->publickey_blob = NULL; - if (!s->we_are_in) { - - /* - * Load the public half of any configured public key file - * for later use. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - logeventf(ssh, "Reading key file \"%.150s\"", - 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 = - ssh2_userkey_loadpub(s->keyfile, - &s->publickey_algorithm, - &s->publickey_bloblen, - &s->publickey_comment, &error); - if (s->publickey_blob) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); - if (!s->privatekey_available) - logeventf(ssh, "Key file contains public key only"); - s->privatekey_encrypted = - ssh2_userkey_encrypted(s->keyfile, NULL); - } else { - char *msgbuf; - logeventf(ssh, "Unable to load key (%s)", - error); - msgbuf = dupprintf("Unable to load key file " - "\"%.150s\" (%s)\r\n", - filename_to_str(s->keyfile), - error); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - } - } else { - char *msgbuf; - logeventf(ssh, "Unable to use this key file (%s)", - key_type_to_str(keytype)); - msgbuf = dupprintf("Unable to use key file \"%.150s\"" - " (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - c_write_str(ssh, msgbuf); - sfree(msgbuf); - s->publickey_blob = NULL; - } - } - - /* - * Find out about any keys Pageant has (but if there's a - * public key configured, filter out all others). - */ - s->nkeys = 0; - s->agent_response = NULL; - s->pkblob_in_agent = NULL; - if (conf_get_int(ssh->conf, CONF_tryagent) && agent_exists()) { - - void *r; - - logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - PUT_32BIT(s->agent_request, 1); - s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; - ssh->auth_agent_query = agent_query( - s->agent_request, 5, &r, &s->agent_responselen, - ssh_agent_callback, ssh); - if (ssh->auth_agent_query) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server while" - " waiting for agent response")); - crStopV; - } - } while (pktin || inlen > 0); - r = ssh->agent_response; - s->agent_responselen = ssh->agent_response_len; - } - s->agent_response = (unsigned char *) r; - if (s->agent_response && s->agent_responselen >= 5 && - s->agent_response[4] == SSH2_AGENT_IDENTITIES_ANSWER) { - int keyi; - unsigned char *p; - p = s->agent_response + 5; - s->nkeys = toint(GET_32BIT(p)); - - /* - * Vet the Pageant response to ensure that the key - * count and blob lengths make sense. - */ - if (s->nkeys < 0) { - logeventf(ssh, "Pageant response contained a negative" - " key count %d", s->nkeys); - s->nkeys = 0; - goto done_agent_query; - } else { - unsigned char *q = p + 4; - int lenleft = s->agent_responselen - 5 - 4; - - for (keyi = 0; keyi < s->nkeys; keyi++) { - int bloblen, commentlen; - if (lenleft < 4) { - logeventf(ssh, "Pageant response was truncated"); - s->nkeys = 0; - goto done_agent_query; - } - bloblen = toint(GET_32BIT(q)); - lenleft -= 4; - q += 4; - if (bloblen < 0 || bloblen > lenleft) { - logeventf(ssh, "Pageant response was truncated"); - s->nkeys = 0; - goto done_agent_query; - } - lenleft -= bloblen; - q += bloblen; - commentlen = toint(GET_32BIT(q)); - lenleft -= 4; - q += 4; - if (commentlen < 0 || commentlen > lenleft) { - logeventf(ssh, "Pageant response was truncated"); - s->nkeys = 0; - goto done_agent_query; - } - lenleft -= commentlen; - q += commentlen; - } - } - - p += 4; - logeventf(ssh, "Pageant has %d SSH-2 keys", s->nkeys); - if (s->publickey_blob) { - /* See if configured key is in agent. */ - for (keyi = 0; keyi < s->nkeys; keyi++) { - s->pklen = toint(GET_32BIT(p)); - if (s->pklen == s->publickey_bloblen && - !memcmp(p+4, s->publickey_blob, - s->publickey_bloblen)) { - logeventf(ssh, "Pageant key #%d matches " - "configured key file", keyi); - s->keyi = keyi; - s->pkblob_in_agent = p; - break; - } - p += 4 + s->pklen; - p += toint(GET_32BIT(p)) + 4; /* comment */ - } - if (!s->pkblob_in_agent) { - logevent("Configured key file not in Pageant"); - s->nkeys = 0; - } - } - } else { - logevent("Failed to get reply from Pageant"); - } - done_agent_query:; - } - - } - - /* - * 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 (!s->we_are_in) { - /* - * Get a username. - */ - if (s->got_username && !conf_get_int(ssh->conf, CONF_change_username)) { - /* - * We got a username last time round this loop, and - * with change_username turned off we don't try to get - * it again. - */ - } else if ((ssh->username = get_remote_username(ssh->conf)) == NULL) { - int ret; /* need not be kept over crReturn */ - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), TRUE); - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* - * get_userpass_input() failed to get a username. - * Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE); - crStopV; - } - ssh->username = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } else { - char *stuff; - if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { - stuff = dupprintf("Using username \"%s\".\r\n", ssh->username); - c_write_str(ssh, stuff); - sfree(stuff); - } - } - s->got_username = TRUE; - - /* - * Send an authentication request using method "none": (a) - * just in case it succeeds, and (b) so that we know what - * authentication methods we can usefully try next. - */ - ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; - - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection");/* service requested */ - ssh2_pkt_addstring(s->pktout, "none"); /* method */ - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_NONE; - s->gotit = FALSE; - s->we_are_in = FALSE; - - s->tried_pubkey_config = FALSE; - s->kbd_inter_refused = FALSE; - - /* Reset agent request state. */ - s->done_agent = FALSE; - if (s->agent_response) { - if (s->pkblob_in_agent) { - s->agentp = s->pkblob_in_agent; - } else { - s->agentp = s->agent_response + 5 + 4; - s->keyi = 0; - } - } - - while (1) { - char *methods = NULL; - int methlen = 0; - - /* - * Wait for the result of the last authentication request. - */ - if (!s->gotit) - crWaitUntilV(pktin); - /* - * Now is a convenient point to spew any banner material - * that we've accumulated. (This should ensure that when - * we exit the auth loop, we haven't any left to deal - * with.) - */ - { - int size = bufchain_size(&ssh->banner); - /* - * Don't show the banner if we're operating in - * non-verbose non-interactive mode. (It's probably - * a script, which means nobody will read the - * banner _anyway_, and moreover the printing of - * the banner will screw up processing on the - * output of (say) plink.) - */ - if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { - char *banner = snewn(size, char); - bufchain_fetch(&ssh->banner, banner, size); - c_write_untrusted(ssh, banner, size); - sfree(banner); - } - bufchain_clear(&ssh->banner); - } - if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { - logevent("Access granted"); - s->we_are_in = s->userauth_success = TRUE; - break; - } - - if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) { - bombout(("Strange packet received during authentication: " - "type %d", pktin->type)); - crStopV; - } - - s->gotit = FALSE; - - /* - * OK, we're now sitting on a USERAUTH_FAILURE message, so - * we can look at the string in it and know what we can - * helpfully try next. - */ - if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - ssh_pkt_getstring(pktin, &methods, &methlen); - if (!ssh2_pkt_getbool(pktin)) { - /* - * We have received an unequivocal Access - * Denied. This can translate to a variety of - * messages, 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) - c_write_str(ssh, "Server refused our key\r\n"); - 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. */ - c_write_str(ssh, "Server refused public-key signature" - " despite accepting key!\r\n"); - logevent("Server refused public-key signature" - " despite accepting key!"); - } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { - /* quiet, so no c_write */ - logevent("Server refused keyboard-interactive authentication"); - } else if (s->type==AUTH_TYPE_GSSAPI) { - /* always quiet, so no c_write */ - /* also, the code down in the GSSAPI block has - * already logged this in the Event Log */ - } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { - logevent("Keyboard-interactive authentication failed"); - c_write_str(ssh, "Access denied\r\n"); - } else { - assert(s->type == AUTH_TYPE_PASSWORD); - logevent("Password authentication failed"); - c_write_str(ssh, "Access denied\r\n"); - - if (conf_get_int(ssh->conf, CONF_change_username)) { - /* XXX perhaps we should allow - * keyboard-interactive to do this too? */ - s->we_are_in = FALSE; - break; - } - } - } else { - c_write_str(ssh, "Further authentication required\r\n"); - logevent("Further authentication required"); - } - - s->can_pubkey = - in_commasep_string("publickey", methods, methlen); - s->can_passwd = - in_commasep_string("password", methods, methlen); - s->can_keyb_inter = conf_get_int(ssh->conf, CONF_try_ki_auth) && - in_commasep_string("keyboard-interactive", methods, methlen); -#ifndef NO_GSSAPI - if (conf_get_int(ssh->conf, CONF_try_gssapi_auth) && - in_commasep_string("gssapi-with-mic", methods, methlen)) { - /* Try loading the GSS libraries and see if we - * have any. */ - if (!ssh->gsslibs) - ssh->gsslibs = ssh_gss_setup(ssh->conf); - s->can_gssapi = (ssh->gsslibs->nlibraries > 0); - } else { - /* No point in even bothering to try to load the - * GSS libraries, if the user configuration and - * server aren't both prepared to attempt GSSAPI - * auth in the first place. */ - s->can_gssapi = FALSE; - } -#endif - } - - ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; - - if (s->can_pubkey && !s->done_agent && s->nkeys) { - - /* - * Attempt public-key authentication using a key from Pageant. - */ - - ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; - - logeventf(ssh, "Trying Pageant key #%d", s->keyi); - - /* Unpack key from agent response */ - s->pklen = toint(GET_32BIT(s->agentp)); - s->agentp += 4; - s->pkblob = (char *)s->agentp; - s->agentp += s->pklen; - s->alglen = toint(GET_32BIT(s->pkblob)); - s->alg = s->pkblob + 4; - s->commentlen = toint(GET_32BIT(s->agentp)); - s->agentp += 4; - s->commentp = (char *)s->agentp; - s->agentp += s->commentlen; - /* s->agentp now points at next key, if any */ - - /* See if server will accept it */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); - /* method */ - ssh2_pkt_addbool(s->pktout, FALSE); /* no signature included */ - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - - /* Offer of key refused. */ - s->gotit = TRUE; - - } else { - - void *vret; - - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with " - "public key \""); - c_write(ssh, s->commentp, s->commentlen); - c_write_str(ssh, "\" from agent\r\n"); - } - - /* - * Server is willing to accept the key. - * Construct a SIGN_REQUEST. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); - /* method */ - ssh2_pkt_addbool(s->pktout, TRUE); /* signature included */ - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->alg, s->alglen); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, s->pkblob, s->pklen); - - /* Ask agent for signature. */ - s->siglen = s->pktout->length - 5 + 4 + - ssh->v2_session_id_len; - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) - s->siglen -= 4; - s->len = 1; /* message type */ - s->len += 4 + s->pklen; /* key blob */ - s->len += 4 + s->siglen; /* data to sign */ - s->len += 4; /* flags */ - s->agentreq = snewn(4 + s->len, char); - PUT_32BIT(s->agentreq, s->len); - s->q = s->agentreq + 4; - *s->q++ = SSH2_AGENTC_SIGN_REQUEST; - PUT_32BIT(s->q, s->pklen); - s->q += 4; - memcpy(s->q, s->pkblob, s->pklen); - s->q += s->pklen; - PUT_32BIT(s->q, s->siglen); - s->q += 4; - /* Now the data to be signed... */ - if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { - PUT_32BIT(s->q, ssh->v2_session_id_len); - s->q += 4; - } - memcpy(s->q, ssh->v2_session_id, - ssh->v2_session_id_len); - s->q += ssh->v2_session_id_len; - memcpy(s->q, s->pktout->data + 5, - s->pktout->length - 5); - s->q += s->pktout->length - 5; - /* And finally the (zero) flags word. */ - PUT_32BIT(s->q, 0); - ssh->auth_agent_query = agent_query( - s->agentreq, s->len + 4, &vret, &s->retlen, - ssh_agent_callback, ssh); - if (ssh->auth_agent_query) { - do { - crReturnV; - if (pktin) { - bombout(("Unexpected data from server" - " while waiting for agent" - " response")); - crStopV; - } - } while (pktin || inlen > 0); - vret = ssh->agent_response; - s->retlen = ssh->agent_response_len; - } - s->ret = vret; - sfree(s->agentreq); - if (s->ret) { - if (s->retlen >= 9 && - s->ret[4] == SSH2_AGENT_SIGN_RESPONSE && - GET_32BIT(s->ret + 5) <= (unsigned)(s->retlen-9)) { - logevent("Sending Pageant's response"); - ssh2_add_sigblob(ssh, s->pktout, - s->pkblob, s->pklen, - s->ret + 9, - GET_32BIT(s->ret + 5)); - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY; - } else { - /* FIXME: less drastic response */ - bombout(("Pageant failed to answer challenge")); - crStopV; - } - } - } - - /* Do we have any keys left to try? */ - if (s->pkblob_in_agent) { - s->done_agent = TRUE; - s->tried_pubkey_config = TRUE; - } else { - s->keyi++; - if (s->keyi >= s->nkeys) - s->done_agent = TRUE; - } - - } else if (s->can_pubkey && s->publickey_blob && - s->privatekey_available && !s->tried_pubkey_config) { - - struct ssh2_userkey *key; /* not live over crReturn */ - char *passphrase; /* not live over crReturn */ - - ssh->pkt_actx = SSH2_PKTCTX_PUBLICKEY; - - s->tried_pubkey_config = TRUE; - - /* - * Try the public key supplied in the configuration. - * - * First, offer the public blob to see if the server is - * willing to accept it. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); /* method */ - ssh2_pkt_addbool(s->pktout, FALSE); - /* no signature included */ - ssh2_pkt_addstring(s->pktout, s->publickey_algorithm); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, - (char *)s->publickey_blob, - s->publickey_bloblen); - ssh2_pkt_send(ssh, s->pktout); - logevent("Offered public key"); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - /* Key refused. Give up. */ - s->gotit = TRUE; /* reconsider message next loop */ - s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; - continue; /* process this new message */ - } - logevent("Offer of public key accepted"); - - /* - * Actually attempt a serious authentication using - * the key. - */ - if (flags & FLAG_VERBOSE) { - c_write_str(ssh, "Authenticating with public key \""); - c_write_str(ssh, s->publickey_comment); - c_write_str(ssh, "\"\r\n"); - } - key = NULL; - while (!key) { - const char *error; /* not live over crReturn */ - if (s->privatekey_encrypted) { - /* - * Get a passphrase from the user. - */ - int ret; /* need not be kept over crReturn */ - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = FALSE; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%.100s\": ", - s->publickey_comment), - FALSE); - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, - in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* Failed to get a passphrase. Terminate. */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, - "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - passphrase = - dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - } else { - passphrase = NULL; /* no passphrase needed */ - } - - /* - * Try decrypting the key. - */ - s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile); - key = ssh2_load_userkey(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)) { - c_write_str(ssh, "Wrong passphrase\r\n"); - key = NULL; - /* and loop again */ - } else { - c_write_str(ssh, "Unable to load private key ("); - c_write_str(ssh, error); - c_write_str(ssh, ")\r\n"); - key = NULL; - break; /* try something else */ - } - } - } - - if (key) { - unsigned char *pkblob, *sigblob, *sigdata; - int pkblob_len, sigblob_len, sigdata_len; - int p; - - /* - * We have loaded the private key and the server - * has announced that it's willing to accept it. - * Hallelujah. Generate a signature and send it. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "publickey"); - /* method */ - ssh2_pkt_addbool(s->pktout, TRUE); - /* signature follows */ - ssh2_pkt_addstring(s->pktout, key->alg->name); - pkblob = key->alg->public_blob(key->data, - &pkblob_len); - ssh2_pkt_addstring_start(s->pktout); - ssh2_pkt_addstring_data(s->pktout, (char *)pkblob, - pkblob_len); - - /* - * The data to be signed is: - * - * string session-id - * - * followed by everything so far placed in the - * outgoing packet. - */ - sigdata_len = s->pktout->length - 5 + 4 + - ssh->v2_session_id_len; - if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID) - sigdata_len -= 4; - sigdata = snewn(sigdata_len, unsigned char); - p = 0; - if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) { - PUT_32BIT(sigdata+p, ssh->v2_session_id_len); - p += 4; - } - memcpy(sigdata+p, ssh->v2_session_id, - ssh->v2_session_id_len); - p += ssh->v2_session_id_len; - memcpy(sigdata+p, s->pktout->data + 5, - s->pktout->length - 5); - p += s->pktout->length - 5; - assert(p == sigdata_len); - sigblob = key->alg->sign(key->data, (char *)sigdata, - sigdata_len, &sigblob_len); - ssh2_add_sigblob(ssh, s->pktout, pkblob, pkblob_len, - sigblob, sigblob_len); - sfree(pkblob); - sfree(sigblob); - sfree(sigdata); - - ssh2_pkt_send(ssh, s->pktout); - logevent("Sent public key signature"); - s->type = AUTH_TYPE_PUBLICKEY; - key->alg->freekey(key->data); - sfree(key->comment); - sfree(key); - } - -#ifndef NO_GSSAPI - } else if (s->can_gssapi && !s->tried_gssapi) { - - /* GSSAPI Authentication */ - - int micoffset, len; - char *data; - Ssh_gss_buf mic; - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi = TRUE; - s->gotit = TRUE; - ssh->pkt_actx = SSH2_PKTCTX_GSSAPI; - - /* - * Pick the highest GSS library on the preference - * list. - */ - { - int i, j; - s->gsslib = NULL; - for (i = 0; i < ngsslibs; i++) { - int want_id = conf_get_int_int(ssh->conf, - CONF_ssh_gsslist, i); - for (j = 0; j < ssh->gsslibs->nlibraries; j++) - if (ssh->gsslibs->libraries[j].id == want_id) { - s->gsslib = &ssh->gsslibs->libraries[j]; - goto got_gsslib; /* double break */ - } - } - got_gsslib: - /* - * We always expect to have found something in - * the above loop: we only came here if there - * was at least one viable GSS library, and the - * preference list should always mention - * everything and only change the order. - */ - assert(s->gsslib); - } - - if (s->gsslib->gsslogmsg) - logevent(s->gsslib->gsslogmsg); - - /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - ssh2_pkt_addstring(s->pktout, "gssapi-with-mic"); - logevent("Attempting GSSAPI authentication"); - - /* add mechanism info */ - s->gsslib->indicate_mech(s->gsslib, &s->gss_buf); - - /* number of GSSAPI mechanisms */ - ssh2_pkt_adduint32(s->pktout,1); - - /* length of OID + 2 */ - ssh2_pkt_adduint32(s->pktout, s->gss_buf.length + 2); - ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE); - - /* length of OID */ - ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.length); - - ssh_pkt_adddata(s->pktout, s->gss_buf.value, - s->gss_buf.length); - ssh2_pkt_send(ssh, s->pktout); - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { - logevent("GSSAPI authentication request refused"); - continue; - } - - /* check returned packet ... */ - - ssh_pkt_getstring(pktin, &data, &len); - s->gss_rcvtok.value = data; - s->gss_rcvtok.length = len; - if (s->gss_rcvtok.length != s->gss_buf.length + 2 || - ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE || - ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length || - memcmp((char *)s->gss_rcvtok.value + 2, - s->gss_buf.value,s->gss_buf.length) ) { - logevent("GSSAPI authentication - wrong response from server"); - continue; - } - - /* now start running */ - s->gss_stat = s->gsslib->import_name(s->gsslib, - ssh->fullhostname, - &s->gss_srv_name); - if (s->gss_stat != SSH_GSS_OK) { - if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) - logevent("GSSAPI import name failed - Bad service name"); - else - logevent("GSSAPI import name failed"); - continue; - } - - /* fetch TGT into GSS engine */ - s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx); - - if (s->gss_stat != SSH_GSS_OK) { - logevent("GSSAPI authentication failed to get credentials"); - s->gsslib->release_name(s->gsslib, &s->gss_srv_name); - continue; - } - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - - /* now enter the loop */ - do { - s->gss_stat = s->gsslib->init_sec_context - (s->gsslib, - &s->gss_ctx, - s->gss_srv_name, - conf_get_int(ssh->conf, CONF_gssapifwd), - &s->gss_rcvtok, - &s->gss_sndtok); - - if (s->gss_stat!=SSH_GSS_S_COMPLETE && - s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { - logevent("GSSAPI authentication initialisation failed"); - - if (s->gsslib->display_status(s->gsslib, s->gss_ctx, - &s->gss_buf) == SSH_GSS_OK) { - logevent(s->gss_buf.value); - sfree(s->gss_buf.value); - } - - break; - } - logevent("GSSAPI authentication initialised"); - - /* Client and server now exchange tokens until GSSAPI - * no longer says CONTINUE_NEEDED */ - - if (s->gss_sndtok.length != 0) { - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN); - ssh_pkt_addstring_start(s->pktout); - ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length); - ssh2_pkt_send(ssh, s->pktout); - s->gsslib->free_tok(s->gsslib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { - logevent("GSSAPI authentication - bad server response"); - s->gss_stat = SSH_GSS_FAILURE; - break; - } - ssh_pkt_getstring(pktin, &data, &len); - s->gss_rcvtok.value = data; - s->gss_rcvtok.length = len; - } - } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (s->gss_stat != SSH_GSS_OK) { - s->gsslib->release_name(s->gsslib, &s->gss_srv_name); - s->gsslib->release_cred(s->gsslib, &s->gss_ctx); - continue; - } - logevent("GSSAPI authentication loop finished OK"); - - /* Now send the MIC */ - - s->pktout = ssh2_pkt_init(0); - micoffset = s->pktout->length; - ssh_pkt_addstring_start(s->pktout); - ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len); - ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST); - ssh_pkt_addstring(s->pktout, ssh->username); - ssh_pkt_addstring(s->pktout, "ssh-connection"); - ssh_pkt_addstring(s->pktout, "gssapi-with-mic"); - - s->gss_buf.value = (char *)s->pktout->data + micoffset; - s->gss_buf.length = s->pktout->length - micoffset; - - s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic); - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC); - ssh_pkt_addstring_start(s->pktout); - ssh_pkt_addstring_data(s->pktout, mic.value, mic.length); - ssh2_pkt_send(ssh, s->pktout); - s->gsslib->free_mic(s->gsslib, &mic); - - s->gotit = FALSE; - - s->gsslib->release_name(s->gsslib, &s->gss_srv_name); - s->gsslib->release_cred(s->gsslib, &s->gss_ctx); - continue; -#endif - } else if (s->can_keyb_inter && !s->kbd_inter_refused) { - - /* - * Keyboard-interactive authentication. - */ - - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - ssh->pkt_actx = SSH2_PKTCTX_KBDINTER; - - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "keyboard-interactive"); - /* method */ - ssh2_pkt_addstring(s->pktout, ""); /* lang */ - ssh2_pkt_addstring(s->pktout, ""); /* submethods */ - ssh2_pkt_send(ssh, s->pktout); - - logevent("Attempting keyboard-interactive authentication"); - - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { - /* Server is not willing to do keyboard-interactive - * at all (or, bizarrely but legally, accepts the - * user without actually issuing any prompts). - * Give up on it entirely. */ - s->gotit = TRUE; - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; - s->kbd_inter_refused = TRUE; /* don't try it again */ - continue; - } - - /* - * Loop while the server continues to send INFO_REQUESTs. - */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - - char *name, *inst, *lang; - int name_len, inst_len, lang_len; - int i; - - /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. - */ - ssh_pkt_getstring(pktin, &name, &name_len); - ssh_pkt_getstring(pktin, &inst, &inst_len); - ssh_pkt_getstring(pktin, &lang, &lang_len); - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - - /* - * Get any prompt(s) from the packet. - */ - s->num_prompts = ssh_pkt_getuint32(pktin); - for (i = 0; i < s->num_prompts; i++) { - char *prompt; - int prompt_len; - int echo; - static char noprompt[] = - ": "; - - ssh_pkt_getstring(pktin, &prompt, &prompt_len); - echo = ssh2_pkt_getbool(pktin); - if (!prompt_len) { - prompt = noprompt; - prompt_len = lenof(noprompt)-1; - } - add_prompt(s->cur_prompt, - dupprintf("%.*s", prompt_len, prompt), - echo); - } - - if (name_len) { - /* FIXME: better prefix to distinguish from - * local prompts? */ - s->cur_prompt->name = - dupprintf("SSH server: %.*s", name_len, name); - s->cur_prompt->name_reqd = TRUE; - } else { - s->cur_prompt->name = - dupstr("SSH server authentication"); - s->cur_prompt->name_reqd = FALSE; - } - /* We add a prefix to try to make it clear that a prompt - * has come from the server. - * FIXME: ugly to print "Using..." in prompt _every_ - * time round. Can this be done more subtly? */ - /* Special case: for reasons best known to themselves, - * some servers send k-i requests with no prompts and - * nothing to display. Keep quiet in this case. */ - if (s->num_prompts || name_len || inst_len) { - s->cur_prompt->instruction = - dupprintf("Using keyboard-interactive authentication.%s%.*s", - inst_len ? "\n" : "", inst_len, inst); - s->cur_prompt->instr_reqd = TRUE; - } else { - s->cur_prompt->instr_reqd = FALSE; - } - - /* - * Display any instructions, and get the user's - * response(s). - */ - { - int ret; /* not live over crReturn */ - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - } - - /* - * Send the response(s) to the server. - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); - ssh2_pkt_adduint32(s->pktout, s->num_prompts); - for (i=0; i < s->num_prompts; i++) { - ssh2_pkt_addstring(s->pktout, - s->cur_prompt->prompts[i]->result); - } - ssh2_pkt_send_with_padding(ssh, s->pktout, 256); - - /* - * 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. - */ - crWaitUntilV(pktin); - - } - - /* - * We should have SUCCESS or FAILURE now. - */ - s->gotit = TRUE; - - } else if (s->can_passwd) { - - /* - * Plain old password authentication. - */ - int ret; /* not live over crReturn */ - int changereq_first_time; /* not live over crReturn */ - - ssh->pkt_actx = SSH2_PKTCTX_PASSWORD; - - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - ssh->username, - ssh->savedhost), - FALSE); - - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - /* - * Squirrel away the password. (We may need it later if - * asked to change it.) - */ - s->password = dupstr(s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); - - /* - * Send the password packet. - * - * We pad out the password packet to 256 bytes to make - * it harder for an attacker to find the length of the - * user's password. - * - * Anyone using a password longer than 256 bytes - * probably doesn't have much to worry about from - * people who find out how long their password is! - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "password"); - ssh2_pkt_addbool(s->pktout, FALSE); - ssh2_pkt_addstring(s->pktout, s->password); - ssh2_pkt_send_with_padding(ssh, s->pktout, 256); - logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; - - /* - * Wait for next packet, in case it's a password change - * request. - */ - crWaitUntilV(pktin); - changereq_first_time = TRUE; - - while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { - - /* - * We're being asked for a new password - * (perhaps not for the first time). - * Loop until the server accepts it. - */ - - int got_new = FALSE; /* not live over crReturn */ - char *prompt; /* not live over crReturn */ - int prompt_len; /* not live over crReturn */ - - { - const char *msg; - if (changereq_first_time) - msg = "Server requested password change"; - else - msg = "Server rejected new password"; - logevent(msg); - c_write_str(ssh, msg); - c_write_str(ssh, "\r\n"); - } - - ssh_pkt_getstring(pktin, &prompt, &prompt_len); - - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - s->cur_prompt->name = dupstr("New SSH password"); - s->cur_prompt->instruction = - dupprintf("%.*s", prompt_len, NULLTOEMPTY(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) { - - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; - } - if (!ret) { - /* - * Failed to get responses. Terminate. - */ - /* burn the evidence */ - free_prompts(s->cur_prompt); - smemclr(s->password, strlen(s->password)); - sfree(s->password); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; - } - - /* - * If the user specified a new original password - * (IYSWIM), overwrite any previously specified - * one. - * (A side effect is that the user doesn't have to - * re-enter it if they louse up the new password.) - */ - if (s->cur_prompt->prompts[0]->result[0]) { - smemclr(s->password, strlen(s->password)); - /* burn the evidence */ - sfree(s->password); - s->password = - dupstr(s->cur_prompt->prompts[0]->result); - } - - /* - * Check the two new passwords match. - */ - got_new = (strcmp(s->cur_prompt->prompts[1]->result, - s->cur_prompt->prompts[2]->result) - == 0); - if (!got_new) - /* They don't. Silly user. */ - c_write_str(ssh, "Passwords do not match\r\n"); - - } - - /* - * Send the new password (along with the old one). - * (see above for padding rationale) - */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); - ssh2_pkt_addstring(s->pktout, ssh->username); - ssh2_pkt_addstring(s->pktout, "ssh-connection"); - /* service requested */ - ssh2_pkt_addstring(s->pktout, "password"); - ssh2_pkt_addbool(s->pktout, TRUE); - ssh2_pkt_addstring(s->pktout, s->password); - ssh2_pkt_addstring(s->pktout, - s->cur_prompt->prompts[1]->result); - free_prompts(s->cur_prompt); - ssh2_pkt_send_with_padding(ssh, s->pktout, 256); - logevent("Sent new password"); - - /* - * Now see what the server has to say about it. - * (If it's CHANGEREQ again, it's not happy with the - * new password.) - */ - crWaitUntilV(pktin); - changereq_first_time = FALSE; - - } - - /* - * We need to reexamine the current pktin at the top - * of the loop. Either: - * - we weren't asked to change password at all, in - * which case it's a SUCCESS or FAILURE with the - * usual meaning - * - we sent a new password, and the server was - * either OK with it (SUCCESS or FAILURE w/partial - * success) or unhappy with the _old_ password - * (FAILURE w/o partial success) - * In any of these cases, we go back to the top of - * the loop and start again. - */ - s->gotit = TRUE; - - /* - * We don't need the old password any more, in any - * case. Burn the evidence. - */ - smemclr(s->password, strlen(s->password)); - sfree(s->password); - - } else { - char *str = dupprintf("No supported authentication methods available" - " (server sent: %.*s)", - methlen, methods); - - ssh_disconnect(ssh, str, - "No supported authentication methods available", - SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - FALSE); - sfree(str); - - crStopV; - - } - - } - } - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL; - - /* Clear up various bits and pieces from authentication. */ - if (s->publickey_blob) { - sfree(s->publickey_algorithm); - sfree(s->publickey_blob); - sfree(s->publickey_comment); - } - if (s->agent_response) - sfree(s->agent_response); - - if (s->userauth_success && !ssh->bare_connection) { - /* - * We've just received USERAUTH_SUCCESS, and we haven't sent any - * packets since. Signal the transport layer to consider enacting - * delayed compression. - * - * (Relying on we_are_in is not sufficient, as - * draft-miller-secsh-compression-delayed is quite clear that it - * triggers on USERAUTH_SUCCESS specifically, and we_are_in can - * become set for other reasons.) - */ - do_ssh2_transport(ssh, "enabling delayed compression", -2, NULL); - } - - ssh->channels = newtree234(ssh_channelcmp); - - /* - * Set up handlers for some connection protocol messages, so we - * don't have to handle them repeatedly in this coroutine. - */ - ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = - ssh2_msg_channel_window_adjust; - ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = - ssh2_msg_global_request; - - /* - * Create the main session channel. - */ - if (conf_get_int(ssh->conf, CONF_ssh_no_shell)) { - ssh->mainchan = NULL; - } else { - ssh->mainchan = snew(struct ssh_channel); - ssh->mainchan->ssh = ssh; - ssh_channel_init(ssh->mainchan); - - if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) { - /* - * Just start a direct-tcpip channel and use it as the main - * channel. - */ - ssh_send_port_open(ssh->mainchan, - conf_get_str(ssh->conf, CONF_ssh_nc_host), - conf_get_int(ssh->conf, CONF_ssh_nc_port), - "main channel"); - ssh->ncmode = TRUE; - } else { - s->pktout = ssh2_chanopen_init(ssh->mainchan, "session"); - logevent("Opening session as main channel"); - ssh2_pkt_send(ssh, s->pktout); - ssh->ncmode = FALSE; - } - crWaitUntilV(pktin); - if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) { - bombout(("Server refused to open channel")); - crStopV; - /* FIXME: error data comes back in FAILURE packet */ - } - if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) { - bombout(("Server's channel confirmation cited wrong channel")); - crStopV; - } - ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin); - ssh->mainchan->halfopen = FALSE; - ssh->mainchan->type = CHAN_MAINSESSION; - ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin); - ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin); - update_specials_menu(ssh->frontend); - logevent("Opened main channel"); - } - - /* - * Now we have a channel, make dispatch table entries for - * general channel-based messages. - */ - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = - ssh2_msg_channel_data; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_channel_eof; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_channel_close; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = - ssh2_msg_channel_open_confirmation; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = - ssh2_msg_channel_open_failure; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = - ssh2_msg_channel_request; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = - ssh2_msg_channel_open; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response; - - /* - * Now the connection protocol is properly up and running, with - * all those dispatch table entries, so it's safe to let - * downstreams start trying to open extra channels through us. - */ - if (ssh->connshare) - share_activate(ssh->connshare, ssh->v_s); - - if (ssh->mainchan && ssh_is_simple(ssh)) { - /* - * This message indicates to the server that we promise - * not to try to run any other channel in parallel with - * this one, so it's safe for it to advertise a very large - * window and leave the flow control to TCP. - */ - s->pktout = ssh2_chanreq_init(ssh->mainchan, - "simple@putty.projects.tartarus.org", - NULL, NULL); - ssh2_pkt_send(ssh, s->pktout); - } - - /* - * Enable port forwardings. - */ - ssh_setup_portfwd(ssh, ssh->conf); - - if (ssh->mainchan && !ssh->ncmode) { - /* - * Send the CHANNEL_REQUESTS for the main session channel. - * Each one is handled by its own little asynchronous - * co-routine. - */ - - /* Potentially enable X11 forwarding. */ - if (conf_get_int(ssh->conf, CONF_x11_forward)) { - ssh->x11disp = - x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display), - ssh->conf); - if (!ssh->x11disp) { - /* FIXME: return an error message from x11_setup_display */ - logevent("X11 forwarding not enabled: unable to" - " initialise X display"); - } else { - ssh->x11auth = x11_invent_fake_auth - (ssh->x11authtree, conf_get_int(ssh->conf, CONF_x11_auth)); - ssh->x11auth->disp = ssh->x11disp; - - ssh2_setup_x11(ssh->mainchan, NULL, NULL); - } - } - - /* Potentially enable agent forwarding. */ - if (ssh_agent_forwarding_permitted(ssh)) - ssh2_setup_agent(ssh->mainchan, NULL, NULL); - - /* Now allocate a pty for the session. */ - if (!conf_get_int(ssh->conf, CONF_nopty)) - ssh2_setup_pty(ssh->mainchan, NULL, NULL); - - /* Send environment variables. */ - ssh2_setup_env(ssh->mainchan, NULL, NULL); - - /* - * Start a shell or a remote command. We may have to attempt - * this twice if the config data has provided a second choice - * of command. - */ - while (1) { - int subsys; - char *cmd; - - if (ssh->fallback_cmd) { - subsys = conf_get_int(ssh->conf, CONF_ssh_subsys2); - cmd = conf_get_str(ssh->conf, CONF_remote_cmd2); - } else { - subsys = conf_get_int(ssh->conf, CONF_ssh_subsys); - cmd = conf_get_str(ssh->conf, CONF_remote_cmd); - } - - if (subsys) { - s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem", - ssh2_response_authconn, NULL); - ssh2_pkt_addstring(s->pktout, cmd); - } else if (*cmd) { - s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec", - ssh2_response_authconn, NULL); - ssh2_pkt_addstring(s->pktout, cmd); - } else { - s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell", - ssh2_response_authconn, NULL); - } - ssh2_pkt_send(ssh, s->pktout); - - crWaitUntilV(pktin); - - if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) { - if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) { - bombout(("Unexpected response to shell/command request:" - " packet type %d", pktin->type)); - crStopV; - } - /* - * We failed to start the command. If this is the - * fallback command, we really are finished; if it's - * not, and if the fallback command exists, try falling - * back to it before complaining. - */ - if (!ssh->fallback_cmd && - *conf_get_str(ssh->conf, CONF_remote_cmd2)) { - logevent("Primary command failed; attempting fallback"); - ssh->fallback_cmd = TRUE; - continue; - } - bombout(("Server refused to start a shell/command")); - crStopV; - } else { - logevent("Started a shell/command"); - } - break; - } - } else { - ssh->editing = ssh->echoing = TRUE; - } + /* 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); - ssh->state = SSH_STATE_SESSION; - if (ssh->size_needed) - ssh_size(ssh, ssh->term_width, ssh->term_height); - if (ssh->eof_needed) - ssh_special(ssh, TS_EOF); + bufchain_add(&ssh->in_raw, data, len); + if (!ssh->frozen && ssh->bpp) + queue_idempotent_callback(&ssh->bpp->ic_in_raw); +} +static void ssh_sent(Plug *plug, int bufsize) +{ + Ssh *ssh = container_of(plug, Ssh, plug); /* - * Transfer data! + * 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 (ssh->ldisc) - ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */ - if (ssh->mainchan) - ssh->send_ok = 1; - while (1) { - crReturnV; - if (pktin) { - - /* - * _All_ the connection-layer packets we expect to - * receive are now handled by the dispatch table. - * Anything that reaches here must be bogus. - */ - - bombout(("Strange packet received: type %d", pktin->type)); - crStopV; - } else if (ssh->mainchan) { - /* - * We have spare data. Add it to the channel buffer. - */ - ssh_send_channel_data(ssh->mainchan, (char *)in, inlen); - } + if (bufsize < SSH_MAX_BACKLOG) { + ssh_throttle_all(ssh, false, bufsize); + queue_idempotent_callback(&ssh->ic_out_raw); } - - crFinishV; } -/* - * Handlers for SSH-2 messages that might arrive at any moment. - */ -static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) +static void ssh_hostport_setup(const char *host, int port, Conf *conf, + char **savedhost, int *savedport, + char **loghost_ret) { - /* log reason code in disconnect message */ - char *buf, *msg; - int reason, msglen; + 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 */ - reason = ssh_pkt_getuint32(pktin); - ssh_pkt_getstring(pktin, &msg, &msglen); + /* + * 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); + } - if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) { - buf = dupprintf("Received disconnect message (%s)", - ssh2_disconnect_reasons[reason]); + *savedhost = host_strduptrim(tmphost); + sfree(tmphost); } else { - buf = dupprintf("Received disconnect message (unknown" - " type %d)", reason); + *savedhost = host_strduptrim(host); + if (port < 0) + port = 22; /* default ssh port */ + *savedport = port; } - logevent(buf); - sfree(buf); - buf = dupprintf("Disconnection message text: %.*s", - msglen, NULLTOEMPTY(msg)); - logevent(buf); - bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"", - reason, - (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? - ssh2_disconnect_reasons[reason] : "unknown", - msglen, NULLTOEMPTY(msg))); - sfree(buf); } -static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) +static bool ssh_test_for_upstream(const char *host, int port, Conf *conf) { - /* log the debug message */ - char *msg; - int msglen; + char *savedhost; + int savedport; + bool ret; - /* XXX maybe we should actually take notice of the return value */ - ssh2_pkt_getbool(pktin); - ssh_pkt_getstring(pktin, &msg, &msglen); + 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(); - logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg)); + return ret; } -static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin) -{ - do_ssh2_transport(ssh, NULL, 0, pktin); -} +static const PlugVtable Ssh_plugvt = { + ssh_socket_log, + ssh_closing, + ssh_receive, + ssh_sent, + NULL +}; /* - * Called if we receive a packet that isn't allowed by the protocol. - * This only applies to packets whose meaning PuTTY understands. - * Entirely unknown packets are handled below. + * 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 void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin) +static const char *connect_to_host( + Ssh *ssh, const char *host, int port, char **realhost, + bool nodelay, bool keepalive) { - char *buf = dupprintf("Server protocol violation: unexpected %s packet", - ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, - pktin->type)); - ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE); - sfree(buf); -} + SockAddr *addr; + const char *err; + char *loghost; + int addressfamily, sshprot; -static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin) -{ - struct Packet *pktout; - pktout = ssh2_pkt_init(SSH2_MSG_UNIMPLEMENTED); - ssh2_pkt_adduint32(pktout, pktin->sequence); - /* - * UNIMPLEMENTED messages MUST appear in the same order as the - * messages they respond to. Hence, never queue them. - */ - ssh2_pkt_send_noqueue(ssh, pktout); -} + ssh_hostport_setup(host, port, ssh->conf, + &ssh->savedhost, &ssh->savedport, &loghost); -/* - * Handle the top-level SSH-2 protocol. - */ -static void ssh2_protocol_setup(Ssh ssh) -{ - int i; + ssh->plug.vt = &Ssh_plugvt; /* - * Most messages cause SSH2_MSG_UNIMPLEMENTED. + * 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. */ - for (i = 0; i < 256; i++) - ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + 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 */ - /* - * Initially, we only accept transport messages (and a few generic - * ones). do_ssh2_authconn will add more when it starts. - * Messages that are understood but not currently acceptable go to - * ssh2_msg_unexpected. - */ - ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport; - /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected; - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */ - /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */ - ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected; + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) { + /* 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(ssh->seat, msg, strlen(msg)); + } + } else { + /* + * We're not a downstream, so open a normal socket. + */ - /* - * These messages have a special handler from the start. - */ - ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; - ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */ - ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; -} + /* + * 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 err; + } + ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ -static void ssh2_bare_connection_protocol_setup(Ssh ssh) -{ - int i; + 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 err; + } + } /* - * Most messages cause SSH2_MSG_UNIMPLEMENTED. + * The SSH version number is always fixed (since we no longer support + * fallback between versions), so set it now. */ - for (i = 0; i < 256; i++) - ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented; + 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; + } /* - * Initially, we set all ssh-connection messages to 'unexpected'; - * do_ssh2_authconn will fill things in properly. We also handle a - * couple of messages from the transport protocol which aren't - * related to key exchange (UNIMPLEMENTED, IGNORE, DEBUG, - * DISCONNECT). + * 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->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected; - ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected; - - ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected; + 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); /* - * These messages have a special handler from the start. + * loghost, if configured, overrides realhost. */ - ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect; - ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; - ssh->packet_dispatch[SSH2_MSG_DEBUG] = ssh2_msg_debug; + if (*loghost) { + sfree(*realhost); + *realhost = dupstr(loghost); + } + + return NULL; } -static void ssh2_timer(void *ctx, unsigned long now) +/* + * Throttle or unthrottle the SSH connection. + */ +void ssh_throttle_conn(Ssh *ssh, int adjust) { - Ssh ssh = (Ssh)ctx; + int old_count = ssh->conn_throttle_count; + bool frozen; - if (ssh->state == SSH_STATE_CLOSED) - return; + ssh->conn_throttle_count += adjust; + assert(ssh->conn_throttle_count >= 0); - if (!ssh->kex_in_progress && !ssh->bare_connection && - conf_get_int(ssh->conf, CONF_ssh_rekey_time) != 0 && - now == ssh->next_rekey) { - do_ssh2_transport(ssh, "timeout", -1, NULL); + 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 */ } -} -static void ssh2_protocol(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin) -{ - const unsigned char *in = (const unsigned char *)vin; - if (ssh->state == SSH_STATE_CLOSED) - return; + ssh->frozen = frozen; - if (pktin) { - ssh->incoming_data_size += pktin->encrypted_len; - if (!ssh->kex_in_progress && - ssh->max_data_size != 0 && - ssh->incoming_data_size > ssh->max_data_size) - do_ssh2_transport(ssh, "too much data received", -1, NULL); - } + if (ssh->s) { + sk_set_frozen(ssh->s, frozen); - if (pktin) - ssh->packet_dispatch[pktin->type](ssh, pktin); - else if (!ssh->protocol_initial_phase_done) - do_ssh2_transport(ssh, in, inlen, pktin); - else - do_ssh2_authconn(ssh, in, inlen, pktin); + /* + * Now process any SSH connection data that was stashed in our + * queue while we were frozen. + */ + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } } -static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen, - struct Packet *pktin) +/* + * 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, int bufsize) { - const unsigned char *in = (const unsigned char *)vin; - if (ssh->state == SSH_STATE_CLOSED) + if (enable == ssh->throttled_all) return; + ssh->throttled_all = enable; + ssh->overall_bufsize = bufsize; - if (pktin) - ssh->packet_dispatch[pktin->type](ssh, pktin); - else - do_ssh2_authconn(ssh, in, inlen, pktin); + ssh_throttle_all_channels(ssh->cl, enable); } -static void ssh_cache_conf_values(Ssh ssh) +static void ssh_cache_conf_values(Ssh *ssh) { - ssh->logomitdata = conf_get_int(ssh->conf, CONF_logomitdata); + ssh->pls.omit_passwords = conf_get_bool(ssh->conf, CONF_logomitpass); + ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata); } /* @@ -11167,135 +791,44 @@ static void ssh_cache_conf_values(Ssh ssh) * * Returns an error message, or NULL on success. */ -static const char *ssh_init(void *frontend_handle, void **backend_handle, - Conf *conf, +static const char *ssh_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, const char *host, int port, char **realhost, - int nodelay, int keepalive) + bool nodelay, bool keepalive) { const char *p; - Ssh ssh; + Ssh *ssh; + + ssh = snew(Ssh); + memset(ssh, 0, sizeof(Ssh)); - ssh = snew(struct ssh_tag); ssh->conf = conf_copy(conf); ssh_cache_conf_values(ssh); - ssh->version = 0; /* when not ready yet */ - ssh->s = NULL; - ssh->cipher = NULL; - ssh->v1_cipher_ctx = NULL; - ssh->crcda_ctx = NULL; - ssh->cscipher = NULL; - ssh->cs_cipher_ctx = NULL; - ssh->sccipher = NULL; - ssh->sc_cipher_ctx = NULL; - ssh->csmac = NULL; - ssh->cs_mac_ctx = NULL; - ssh->scmac = NULL; - ssh->sc_mac_ctx = NULL; - ssh->cscomp = NULL; - ssh->cs_comp_ctx = NULL; - ssh->sccomp = NULL; - ssh->sc_comp_ctx = NULL; - ssh->kex = NULL; - ssh->kex_ctx = NULL; - ssh->hostkey = NULL; - ssh->hostkey_str = NULL; ssh->exitcode = -1; - ssh->close_expected = FALSE; - ssh->clean_exit = FALSE; - ssh->state = SSH_STATE_PREPACKET; - ssh->size_needed = FALSE; - ssh->eof_needed = FALSE; - ssh->ldisc = NULL; - ssh->logctx = NULL; - ssh->deferred_send_data = NULL; - ssh->deferred_len = 0; - ssh->deferred_size = 0; - ssh->fallback_cmd = 0; - ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; - ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; - ssh->x11disp = NULL; - ssh->x11auth = NULL; - ssh->x11authtree = newtree234(x11_authcmp); - ssh->v1_compressing = FALSE; - ssh->v2_outgoing_sequence = 0; - ssh->ssh1_rdpkt_crstate = 0; - ssh->ssh2_rdpkt_crstate = 0; - ssh->ssh2_bare_rdpkt_crstate = 0; - ssh->ssh_gotdata_crstate = 0; - ssh->do_ssh1_connection_crstate = 0; - ssh->do_ssh_init_state = NULL; - ssh->do_ssh_connection_init_state = NULL; - ssh->do_ssh1_login_state = NULL; - ssh->do_ssh2_transport_state = NULL; - ssh->do_ssh2_authconn_state = NULL; - ssh->v_c = NULL; - ssh->v_s = NULL; - ssh->mainchan = NULL; - ssh->throttled_all = 0; - ssh->v1_stdout_throttling = 0; - ssh->queue = NULL; - ssh->queuelen = ssh->queuesize = 0; - ssh->queueing = FALSE; - ssh->qhead = ssh->qtail = NULL; - ssh->deferred_rekey_reason = NULL; - bufchain_init(&ssh->queued_incoming_data); - ssh->frozen = FALSE; - ssh->username = NULL; - ssh->sent_console_eof = FALSE; - ssh->got_pty = FALSE; - ssh->bare_connection = FALSE; - ssh->X11_fwd_enabled = FALSE; - ssh->connshare = NULL; - ssh->attempting_connshare = FALSE; - ssh->session_started = FALSE; - ssh->specials = NULL; - ssh->n_uncert_hostkeys = 0; - ssh->cross_certifying = FALSE; - - *backend_handle = ssh; - -#ifdef MSCRYPTOAPI - if (crypto_startup() == 0) - return "Microsoft high encryption pack not installed!"; -#endif - - ssh->frontend = frontend_handle; - ssh->term_width = conf_get_int(ssh->conf, CONF_width); - ssh->term_height = conf_get_int(ssh->conf, CONF_height); + 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->channels = NULL; - ssh->rportfwds = NULL; - ssh->portfwds = NULL; + ssh->backend.vt = &ssh_backend; + *backend_handle = &ssh->backend; - ssh->send_ok = 0; - ssh->editing = 0; - ssh->echoing = 0; - ssh->conn_throttle_count = 0; - ssh->overall_bufsize = 0; - ssh->fallback_cmd = 0; - - ssh->protocol = NULL; - - ssh->protocol_initial_phase_done = FALSE; - - ssh->pinger = NULL; - - ssh->incoming_data_size = ssh->outgoing_data_size = - ssh->deferred_data_size = 0L; - ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, - CONF_ssh_rekey_data)); - ssh->kex_in_progress = FALSE; - - ssh->auth_agent_query = NULL; - -#ifndef NO_GSSAPI - ssh->gsslibs = NULL; -#endif + ssh->seat = seat; + ssh->cl_dummy.logctx = ssh->logctx = logctx; random_ref(); /* do this now - may be needed by sharing setup code */ + ssh->need_random_unref = true; p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); if (p != NULL) { + /* 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 p; } @@ -11303,624 +836,227 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, return NULL; } -static void ssh_free(void *handle) +static void ssh_free(Backend *be) { - Ssh ssh = (Ssh) handle; - struct ssh_channel *c; - struct ssh_rportfwd *pf; - struct X11FakeAuth *auth; - - if (ssh->v1_cipher_ctx) - ssh->cipher->free_context(ssh->v1_cipher_ctx); - if (ssh->cs_cipher_ctx) - ssh->cscipher->free_context(ssh->cs_cipher_ctx); - if (ssh->sc_cipher_ctx) - ssh->sccipher->free_context(ssh->sc_cipher_ctx); - if (ssh->cs_mac_ctx) - ssh->csmac->free_context(ssh->cs_mac_ctx); - if (ssh->sc_mac_ctx) - ssh->scmac->free_context(ssh->sc_mac_ctx); - if (ssh->cs_comp_ctx) { - if (ssh->cscomp) - ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx); - else - zlib_compress_cleanup(ssh->cs_comp_ctx); - } - if (ssh->sc_comp_ctx) { - if (ssh->sccomp) - ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx); - else - zlib_decompress_cleanup(ssh->sc_comp_ctx); - } - if (ssh->kex_ctx) - dh_cleanup(ssh->kex_ctx); - sfree(ssh->savedhost); - - while (ssh->queuelen-- > 0) - ssh_free_packet(ssh->queue[ssh->queuelen]); - sfree(ssh->queue); + Ssh *ssh = container_of(be, Ssh, backend); + bool need_random_unref; - while (ssh->qhead) { - struct queued_handler *qh = ssh->qhead; - ssh->qhead = qh->next; - sfree(qh); - } - ssh->qhead = ssh->qtail = NULL; - - if (ssh->channels) { - while ((c = delpos234(ssh->channels, 0)) != NULL) { - ssh_channel_close_local(c, NULL); - if (ssh->version == 2) { - struct outstanding_channel_request *ocr, *nocr; - ocr = c->v.v2.chanreq_head; - while (ocr) { - ocr->handler(c, NULL, ocr->ctx); - nocr = ocr->next; - sfree(ocr); - ocr = nocr; - } - bufchain_clear(&c->v.v2.outbuffer); - } - sfree(c); - } - freetree234(ssh->channels); - ssh->channels = NULL; - } + ssh_shutdown(ssh); + conf_free(ssh->conf); if (ssh->connshare) sharestate_free(ssh->connshare); - - if (ssh->rportfwds) { - while ((pf = delpos234(ssh->rportfwds, 0)) != NULL) - free_rportfwd(pf); - freetree234(ssh->rportfwds); - ssh->rportfwds = NULL; - } - sfree(ssh->deferred_send_data); - if (ssh->x11disp) - x11_free_display(ssh->x11disp); - while ((auth = delpos234(ssh->x11authtree, 0)) != NULL) - x11_free_fake_auth(auth); - freetree234(ssh->x11authtree); - sfree(ssh->do_ssh_init_state); - sfree(ssh->do_ssh1_login_state); - sfree(ssh->do_ssh2_transport_state); - sfree(ssh->do_ssh2_authconn_state); - sfree(ssh->v_c); - sfree(ssh->v_s); + sfree(ssh->savedhost); sfree(ssh->fullhostname); - sfree(ssh->hostkey_str); sfree(ssh->specials); - if (ssh->crcda_ctx) { - crcda_free_context(ssh->crcda_ctx); - ssh->crcda_ctx = NULL; - } - if (ssh->s) - ssh_do_close(ssh, TRUE); - expire_timer_context(ssh); - if (ssh->pinger) - pinger_free(ssh->pinger); - bufchain_clear(&ssh->queued_incoming_data); - sfree(ssh->username); - conf_free(ssh->conf); - - if (ssh->auth_agent_query) - agent_cancel_query(ssh->auth_agent_query); #ifndef NO_GSSAPI - if (ssh->gsslibs) - ssh_gss_cleanup(ssh->gsslibs); + 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 + + need_random_unref = ssh->need_random_unref; sfree(ssh); - random_unref(); + if (need_random_unref) + random_unref(); } /* * Reconfigure the SSH backend. */ -static void ssh_reconfig(void *handle, Conf *conf) +static void ssh_reconfig(Backend *be, Conf *conf) { - Ssh ssh = (Ssh) handle; - const char *rekeying = NULL; - int rekey_mandatory = FALSE; - unsigned long old_max_data_size; - int i, rekey_time; - - pinger_reconfig(ssh->pinger, ssh->conf, conf); - if (ssh->portfwds) - ssh_setup_portfwd(ssh, conf); - - rekey_time = conf_get_int(conf, CONF_ssh_rekey_time); - if (conf_get_int(ssh->conf, CONF_ssh_rekey_time) != rekey_time && - rekey_time != 0) { - unsigned long new_next = ssh->last_rekey + rekey_time*60*TICKSPERSEC; - unsigned long now = GETTICKCOUNT(); - - if (now - ssh->last_rekey > rekey_time*60*TICKSPERSEC) { - rekeying = "timeout shortened"; - } else { - ssh->next_rekey = schedule_timer(new_next - now, ssh2_timer, ssh); - } - } - - old_max_data_size = ssh->max_data_size; - ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf, - CONF_ssh_rekey_data)); - if (old_max_data_size != ssh->max_data_size && - ssh->max_data_size != 0) { - if (ssh->outgoing_data_size > ssh->max_data_size || - ssh->incoming_data_size > ssh->max_data_size) - rekeying = "data limit lowered"; - } + Ssh *ssh = container_of(be, Ssh, backend); - if (conf_get_int(ssh->conf, CONF_compression) != - conf_get_int(conf, CONF_compression)) { - rekeying = "compression setting changed"; - rekey_mandatory = TRUE; - } + if (ssh->pinger) + pinger_reconfig(ssh->pinger, ssh->conf, conf); - for (i = 0; i < CIPHER_MAX; i++) - if (conf_get_int_int(ssh->conf, CONF_ssh_cipherlist, i) != - conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - rekeying = "cipher settings changed"; - rekey_mandatory = TRUE; - } - if (conf_get_int(ssh->conf, CONF_ssh2_des_cbc) != - conf_get_int(conf, CONF_ssh2_des_cbc)) { - rekeying = "cipher settings changed"; - rekey_mandatory = TRUE; - } + ssh_ppl_reconfigure(ssh->base_layer, conf); conf_free(ssh->conf); ssh->conf = conf_copy(conf); ssh_cache_conf_values(ssh); - - if (!ssh->bare_connection && rekeying) { - if (!ssh->kex_in_progress) { - do_ssh2_transport(ssh, rekeying, -1, NULL); - } else if (rekey_mandatory) { - ssh->deferred_rekey_reason = rekeying; - } - } } /* * Called to send data down the SSH connection. */ -static int ssh_send(void *handle, const char *buf, int len) +static int ssh_send(Backend *be, const char *buf, int len) { - Ssh ssh = (Ssh) handle; + Ssh *ssh = container_of(be, Ssh, backend); - if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) + if (ssh == NULL || ssh->s == NULL) return 0; - ssh->protocol(ssh, (const unsigned char *)buf, len, 0); + bufchain_add(&ssh->user_input, buf, len); + if (ssh->base_layer) + ssh_ppl_got_user_input(ssh->base_layer); - return ssh_sendbuffer(ssh); + return backend_sendbuffer(&ssh->backend); } /* * Called to query the current amount of buffered stdin data. */ -static int ssh_sendbuffer(void *handle) +static int ssh_sendbuffer(Backend *be) { - Ssh ssh = (Ssh) handle; - int override_value; + Ssh *ssh = container_of(be, Ssh, backend); + int backlog; - if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL) + if (!ssh || !ssh->s || !ssh->cl) return 0; + backlog = ssh_stdin_backlog(ssh->cl); + + /* FIXME: also include sizes of pqs */ + /* * If the SSH socket itself has backed up, add the total backup * size on that to any individual buffer on the stdin channel. */ - override_value = 0; if (ssh->throttled_all) - override_value = ssh->overall_bufsize; - - if (ssh->version == 1) { - return override_value; - } else if (ssh->version == 2) { - if (!ssh->mainchan) - return override_value; - else - return (override_value + - bufchain_size(&ssh->mainchan->v.v2.outbuffer)); - } + backlog += ssh->overall_bufsize; - return 0; + return backlog; } /* * Called to set the size of the window from SSH's POV. */ -static void ssh_size(void *handle, int width, int height) +static void ssh_size(Backend *be, int width, int height) { - Ssh ssh = (Ssh) handle; - struct Packet *pktout; + 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); +} - switch (ssh->state) { - case SSH_STATE_BEFORE_SIZE: - case SSH_STATE_PREPACKET: - case SSH_STATE_CLOSED: - break; /* do nothing */ - case SSH_STATE_INTERMED: - ssh->size_needed = TRUE; /* buffer for later */ - break; - case SSH_STATE_SESSION: - if (!conf_get_int(ssh->conf, CONF_nopty)) { - if (ssh->version == 1) { - send_packet(ssh, SSH1_CMSG_WINDOW_SIZE, - PKT_INT, ssh->term_height, - PKT_INT, ssh->term_width, - PKT_INT, 0, PKT_INT, 0, PKT_END); - } else if (ssh->mainchan) { - pktout = ssh2_chanreq_init(ssh->mainchan, "window-change", - NULL, NULL); - ssh2_pkt_adduint32(pktout, ssh->term_width); - ssh2_pkt_adduint32(pktout, ssh->term_height); - ssh2_pkt_adduint32(pktout, 0); - ssh2_pkt_adduint32(pktout, 0); - ssh2_pkt_send(ssh, pktout); - } - } - break; +struct ssh_add_special_ctx { + SessionSpecial *specials; + int 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; + + if (ctx->nspecials >= ctx->specials_size) { + ctx->specials_size = ctx->nspecials * 5 / 4 + 32; + ctx->specials = sresize(ctx->specials, ctx->specials_size, + SessionSpecial); } + + 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 struct telnet_special *ssh_get_specials(void *handle) +static const SessionSpecial *ssh_get_specials(Backend *be) { - static const struct telnet_special ssh1_ignore_special[] = { - {"IGNORE message", TS_NOP} - }; - static const struct telnet_special ssh2_ignore_special[] = { - {"IGNORE message", TS_NOP}, - }; - static const struct telnet_special ssh2_rekey_special[] = { - {"Repeat key exchange", TS_REKEY}, - }; - static const struct telnet_special ssh2_session_specials[] = { - {NULL, TS_SEP}, - {"Break", TS_BRK}, - /* These are the signal names defined by RFC 4254. - * They include all the ISO C signals, but are a subset of the POSIX - * required signals. */ - {"SIGINT (Interrupt)", TS_SIGINT}, - {"SIGTERM (Terminate)", TS_SIGTERM}, - {"SIGKILL (Kill)", TS_SIGKILL}, - {"SIGQUIT (Quit)", TS_SIGQUIT}, - {"SIGHUP (Hangup)", TS_SIGHUP}, - {"More signals", TS_SUBMENU}, - {"SIGABRT", TS_SIGABRT}, {"SIGALRM", TS_SIGALRM}, - {"SIGFPE", TS_SIGFPE}, {"SIGILL", TS_SIGILL}, - {"SIGPIPE", TS_SIGPIPE}, {"SIGSEGV", TS_SIGSEGV}, - {"SIGUSR1", TS_SIGUSR1}, {"SIGUSR2", TS_SIGUSR2}, - {NULL, TS_EXITMENU} - }; - static const struct telnet_special specials_end[] = { - {NULL, TS_EXITMENU} - }; - - struct telnet_special *specials = NULL; - int nspecials = 0, specialsize = 0; - - Ssh ssh = (Ssh) handle; + Ssh *ssh = container_of(be, Ssh, backend); - sfree(ssh->specials); + /* + * Ask all our active protocol layers what specials they've got, + * and amalgamate the list into one combined one. + */ -#define ADD_SPECIALS(name) do \ - { \ - int len = lenof(name); \ - if (nspecials + len > specialsize) { \ - specialsize = (nspecials + len) * 5 / 4 + 32; \ - specials = sresize(specials, specialsize, struct telnet_special); \ - } \ - memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \ - nspecials += len; \ - } while (0) - - if (ssh->version == 1) { - /* Don't bother offering IGNORE if we've decided the remote - * won't cope with it, since we wouldn't bother sending it if - * asked anyway. */ - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) - ADD_SPECIALS(ssh1_ignore_special); - } else if (ssh->version == 2) { - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) - ADD_SPECIALS(ssh2_ignore_special); - if (!(ssh->remote_bugs & BUG_SSH2_REKEY) && !ssh->bare_connection) - ADD_SPECIALS(ssh2_rekey_special); - if (ssh->mainchan) - ADD_SPECIALS(ssh2_session_specials); - - if (ssh->n_uncert_hostkeys) { - static const struct telnet_special uncert_start[] = { - {NULL, TS_SEP}, - {"Cache new host key type", TS_SUBMENU}, - }; - static const struct telnet_special uncert_end[] = { - {NULL, TS_EXITMENU}, - }; - int i; - - ADD_SPECIALS(uncert_start); - for (i = 0; i < ssh->n_uncert_hostkeys; i++) { - struct telnet_special uncert[1]; - const struct ssh_signkey *alg = - hostkey_algs[ssh->uncert_hostkeys[i]].alg; - uncert[0].name = alg->name; - uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i]; - ADD_SPECIALS(uncert); - } - ADD_SPECIALS(uncert_end); - } - } /* else we're not ready yet */ + struct ssh_add_special_ctx ctx; - if (nspecials) - ADD_SPECIALS(specials_end); + ctx.specials = NULL; + ctx.nspecials = ctx.specials_size = 0; - ssh->specials = specials; + if (ssh->base_layer) + ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, &ctx); - if (nspecials) { - return specials; - } else { - return NULL; + if (ctx.specials) { + /* If the list is non-empty, terminate it with a SS_EXITMENU. */ + ssh_add_special(&ctx, NULL, SS_EXITMENU, 0); } -#undef ADD_SPECIALS + + sfree(ssh->specials); + ssh->specials = ctx.specials; + return ssh->specials; } /* - * Send special codes. TS_EOF is useful for `plink', so you - * can send an EOF and collect resulting output (e.g. `plink - * hostname sort'). + * Send special codes. */ -static void ssh_special(void *handle, Telnet_Special code) -{ - Ssh ssh = (Ssh) handle; - struct Packet *pktout; - - if (code == TS_EOF) { - if (ssh->state != SSH_STATE_SESSION) { - /* - * Buffer the EOF in case we are pre-SESSION, so we can - * send it as soon as we reach SESSION. - */ - if (code == TS_EOF) - ssh->eof_needed = TRUE; - return; - } - if (ssh->version == 1) { - send_packet(ssh, SSH1_CMSG_EOF, PKT_END); - } else if (ssh->mainchan) { - sshfwd_write_eof(ssh->mainchan); - ssh->send_ok = 0; /* now stop trying to read from stdin */ - } - logevent("Sent EOF message"); - } else if (code == TS_PING || code == TS_NOP) { - if (ssh->state == SSH_STATE_CLOSED - || ssh->state == SSH_STATE_PREPACKET) return; - if (ssh->version == 1) { - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) - send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END); - } else { - if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - pktout = ssh2_pkt_init(SSH2_MSG_IGNORE); - ssh2_pkt_addstring_start(pktout); - ssh2_pkt_send_noqueue(ssh, pktout); - } - } - } else if (code == TS_REKEY) { - if (!ssh->kex_in_progress && !ssh->bare_connection && - ssh->version == 2) { - do_ssh2_transport(ssh, "at user request", -1, NULL); - } - } else if (code >= TS_LOCALSTART) { - ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg; - ssh->cross_certifying = TRUE; - if (!ssh->kex_in_progress && !ssh->bare_connection && - ssh->version == 2) { - do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL); - } - } else if (code == TS_BRK) { - if (ssh->state == SSH_STATE_CLOSED - || ssh->state == SSH_STATE_PREPACKET) return; - if (ssh->version == 1) { - logevent("Unable to send BREAK signal in SSH-1"); - } else if (ssh->mainchan) { - pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL); - ssh2_pkt_adduint32(pktout, 0); /* default break length */ - ssh2_pkt_send(ssh, pktout); - } - } else { - /* Is is a POSIX signal? */ - const char *signame = NULL; - if (code == TS_SIGABRT) signame = "ABRT"; - if (code == TS_SIGALRM) signame = "ALRM"; - if (code == TS_SIGFPE) signame = "FPE"; - if (code == TS_SIGHUP) signame = "HUP"; - if (code == TS_SIGILL) signame = "ILL"; - if (code == TS_SIGINT) signame = "INT"; - if (code == TS_SIGKILL) signame = "KILL"; - if (code == TS_SIGPIPE) signame = "PIPE"; - if (code == TS_SIGQUIT) signame = "QUIT"; - if (code == TS_SIGSEGV) signame = "SEGV"; - if (code == TS_SIGTERM) signame = "TERM"; - if (code == TS_SIGUSR1) signame = "USR1"; - if (code == TS_SIGUSR2) signame = "USR2"; - /* The SSH-2 protocol does in principle support arbitrary named - * signals, including signame@domain, but we don't support those. */ - if (signame) { - /* It's a signal. */ - if (ssh->version == 2 && ssh->mainchan) { - pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL); - ssh2_pkt_addstring(pktout, signame); - ssh2_pkt_send(ssh, pktout); - logeventf(ssh, "Sent signal SIG%s", signame); - } - } else { - /* Never heard of it. Do nothing */ - } - } -} - -void *new_sock_channel(void *handle, struct PortForwarding *pf) -{ - Ssh ssh = (Ssh) handle; - struct ssh_channel *c; - c = snew(struct ssh_channel); - - c->ssh = ssh; - ssh_channel_init(c); - c->halfopen = TRUE; - c->type = CHAN_SOCKDATA;/* identify channel type */ - c->u.pfd.pf = pf; - return c; -} - -unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx) +static void ssh_special(Backend *be, SessionSpecialCode code, int arg) { - struct ssh_channel *c; - c = snew(struct ssh_channel); - - c->ssh = ssh; - ssh_channel_init(c); - c->type = CHAN_SHARING; - c->u.sharing.ctx = sharing_ctx; - return c->localid; -} - -void ssh_delete_sharing_channel(Ssh ssh, unsigned localid) -{ - struct ssh_channel *c; - - c = find234(ssh->channels, &localid, ssh_channelfind); - if (c) - ssh_channel_destroy(c); -} - -void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, - const void *data, int datalen, - const char *additional_log_text) -{ - struct Packet *pkt; + Ssh *ssh = container_of(be, Ssh, backend); - pkt = ssh2_pkt_init(type); - pkt->downstream_id = id; - pkt->additional_log_text = additional_log_text; - ssh2_pkt_adddata(pkt, data, datalen); - ssh2_pkt_send(ssh, pkt); + if (ssh->base_layer) + ssh_ppl_special_cmd(ssh->base_layer, code, arg); } /* - * This is called when stdout/stderr (the entity to which - * from_backend sends data) manages to clear some backlog. + * This is called when the seat's output channel manages to clear some + * backlog. */ -static void ssh_unthrottle(void *handle, int bufsize) +static void ssh_unthrottle(Backend *be, int bufsize) { - Ssh ssh = (Ssh) handle; - - if (ssh->version == 1) { - if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { - ssh->v1_stdout_throttling = 0; - ssh_throttle_conn(ssh, -1); - } - } else { - if (ssh->mainchan) - ssh_channel_unthrottle(ssh->mainchan, bufsize); - } + Ssh *ssh = container_of(be, Ssh, backend); - /* - * Now process any SSH connection data that was stashed in our - * queue while we were frozen. - */ - ssh_process_queued_incoming_data(ssh); + ssh_stdout_unthrottle(ssh->cl, bufsize); } -void ssh_send_port_open(void *channel, const char *hostname, int port, - const char *org) +static bool ssh_connected(Backend *be) { - struct ssh_channel *c = (struct ssh_channel *)channel; - Ssh ssh = c->ssh; - struct Packet *pktout; - - logeventf(ssh, "Opening connection to %s:%d for %s", hostname, port, org); - - if (ssh->version == 1) { - send_packet(ssh, SSH1_MSG_PORT_OPEN, - PKT_INT, c->localid, - PKT_STR, hostname, - PKT_INT, port, - /* PKT_STR, , */ - PKT_END); - } else { - pktout = ssh2_chanopen_init(c, "direct-tcpip"); - { - char *trimmed_host = host_strduptrim(hostname); - ssh2_pkt_addstring(pktout, trimmed_host); - sfree(trimmed_host); - } - ssh2_pkt_adduint32(pktout, port); - /* - * We make up values for the originator data; partly it's - * too much hassle to keep track, and partly I'm not - * convinced the server should be told details like that - * about my local network configuration. - * The "originator IP address" is syntactically a numeric - * IP address, and some servers (e.g., Tectia) get upset - * if it doesn't match this syntax. - */ - ssh2_pkt_addstring(pktout, "0.0.0.0"); - ssh2_pkt_adduint32(pktout, 0); - ssh2_pkt_send(ssh, pktout); - } + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->s != NULL; } -static int ssh_connected(void *handle) +static bool ssh_sendok(Backend *be) { - Ssh ssh = (Ssh) handle; - return ssh->s != NULL; + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->base_layer && ssh_ppl_want_user_input(ssh->base_layer); } -static int ssh_sendok(void *handle) +void ssh_ldisc_update(Ssh *ssh) { - Ssh ssh = (Ssh) handle; - return ssh->send_ok; + /* Called when the connection layer wants to propagate an update + * to the line discipline options */ + if (ssh->ldisc) + ldisc_echoedit_update(ssh->ldisc); } -static int ssh_ldisc(void *handle, int option) +static bool ssh_ldisc(Backend *be, int option) { - Ssh ssh = (Ssh) handle; - if (option == LD_ECHO) - return ssh->echoing; - if (option == LD_EDIT) - return ssh->editing; - return FALSE; + Ssh *ssh = container_of(be, Ssh, backend); + return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : false; } -static void ssh_provide_ldisc(void *handle, void *ldisc) +static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc) { - Ssh ssh = (Ssh) handle; + Ssh *ssh = container_of(be, Ssh, backend); ssh->ldisc = ldisc; } -static void ssh_provide_logctx(void *handle, void *logctx) +void ssh_got_exitcode(Ssh *ssh, int exitcode) { - Ssh ssh = (Ssh) handle; - ssh->logctx = logctx; + ssh->exitcode = exitcode; } -static int ssh_return_exitcode(void *handle) +static int ssh_return_exitcode(Backend *be) { - Ssh ssh = (Ssh) handle; - if (ssh->s != NULL) + 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); @@ -11931,9 +1067,9 @@ static int ssh_return_exitcode(void *handle) * (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(void *handle) +static int ssh_cfg_info(Backend *be) { - Ssh ssh = (Ssh) handle; + Ssh *ssh = container_of(be, Ssh, backend); if (ssh->version == 0) return 0; /* don't know yet */ else if (ssh->bare_connection) @@ -11947,13 +1083,18 @@ static int ssh_cfg_info(void *handle) * that fails. This variable is the means by which scp.c can reach * into the SSH code and find out which one it got. */ -extern int ssh_fallback_cmd(void *handle) +extern bool ssh_fallback_cmd(Backend *be) { - Ssh ssh = (Ssh) handle; + Ssh *ssh = container_of(be, Ssh, backend); return ssh->fallback_cmd; } -Backend ssh_backend = { +void ssh_got_fallback_cmd(Ssh *ssh) +{ + ssh->fallback_cmd = true; +} + +const struct BackendVtable ssh_backend = { ssh_init, ssh_free, ssh_reconfig, @@ -11967,7 +1108,6 @@ Backend ssh_backend = { ssh_sendok, ssh_ldisc, ssh_provide_ldisc, - ssh_provide_logctx, ssh_unthrottle, ssh_cfg_info, ssh_test_for_upstream, diff --git a/ssh.h b/ssh.h index 371de837..93461532 100644 --- a/ssh.h +++ b/ssh.h @@ -4,53 +4,180 @@ #include "puttymem.h" #include "tree234.h" #include "network.h" -#include "int64.h" #include "misc.h" struct ssh_channel; -typedef struct ssh_tag *Ssh; - -extern int sshfwd_write(struct ssh_channel *c, char *, int); -extern void sshfwd_write_eof(struct ssh_channel *c); -extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err); -extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize); -Conf *sshfwd_get_conf(struct ssh_channel *c); -void sshfwd_x11_sharing_handover(struct ssh_channel *c, - void *share_cs, void *share_chan, - const char *peer_addr, int peer_port, - int endian, int protomajor, int protominor, - const void *initial_data, int initial_len); -void sshfwd_x11_is_local(struct ssh_channel *c); - -extern Socket ssh_connection_sharing_init(const char *host, int port, - Conf *conf, Ssh ssh, void **state); -int ssh_share_test_for_upstream(const char *host, int port, Conf *conf); -void share_got_pkt_from_server(void *ctx, int type, - unsigned char *pkt, int pktlen); -void share_activate(void *state, const char *server_verstring); -void sharestate_free(void *state); -int share_ndownstreams(void *state); - -void ssh_connshare_log(Ssh ssh, int event, const char *logtext, + +/* + * Buffer management constants. There are several of these for + * various different purposes: + * + * - SSH1_BUFFER_LIMIT is the amount of backlog that must build up + * on a local data stream before we throttle the whole SSH + * connection (in SSH-1 only). Throttling the whole connection is + * pretty drastic so we set this high in the hope it won't + * happen very often. + * + * - SSH_MAX_BACKLOG is the amount of backlog that must build up + * on the SSH connection itself before we defensively throttle + * _all_ local data streams. This is pretty drastic too (though + * thankfully unlikely in SSH-2 since the window mechanism should + * ensure that the server never has any need to throttle its end + * of the connection), so we set this high as well. + * + * - OUR_V2_WINSIZE is the default window size we present on SSH-2 + * channels. + * + * - OUR_V2_BIGWIN is the window size we advertise for the only + * channel in a simple connection. It must be <= INT_MAX. + * + * - OUR_V2_MAXPKT is the official "maximum packet size" we send + * to the remote side. This actually has nothing to do with the + * size of the _packet_, but is instead a limit on the amount + * of data we're willing to receive in a single SSH2 channel + * data message. + * + * - OUR_V2_PACKETLIMIT is actually the maximum size of SSH + * _packet_ we're prepared to cope with. It must be a multiple + * of the cipher block size, and must be at least 35000. + */ + +#define SSH1_BUFFER_LIMIT 32768 +#define SSH_MAX_BACKLOG 32768 +#define OUR_V2_WINSIZE 16384 +#define OUR_V2_BIGWIN 0x7fffffff +#define OUR_V2_MAXPKT 0x4000UL +#define OUR_V2_PACKETLIMIT 0x9000UL + +typedef struct PacketQueueNode PacketQueueNode; +struct PacketQueueNode { + PacketQueueNode *next, *prev; + bool on_free_queue; /* is this packet scheduled for freeing? */ +}; + +typedef struct PktIn { + int type; + unsigned long sequence; /* SSH-2 incoming sequence number */ + PacketQueueNode qnode; /* for linking this packet on to a queue */ + BinarySource_IMPLEMENTATION; +} PktIn; + +typedef struct PktOut { + long prefix; /* bytes up to and including type field */ + long length; /* total bytes, including prefix */ + int type; + long minlen; /* SSH-2: ensure wire length is at least this */ + unsigned char *data; /* allocated storage */ + long maxlen; /* amount of storage allocated for `data' */ + + /* Extra metadata used in SSH packet logging mode, allowing us to + * log in the packet header line that the packet came from a + * connection-sharing downstream and what if anything unusual was + * done to it. The additional_log_text field is expected to be a + * static string - it will not be freed. */ + unsigned downstream_id; + const char *additional_log_text; + + PacketQueueNode qnode; /* for linking this packet on to a queue */ + BinarySink_IMPLEMENTATION; +} PktOut; + +typedef struct PacketQueueBase { + PacketQueueNode end; + struct IdempotentCallback *ic; +} PacketQueueBase; + +typedef struct PktInQueue { + PacketQueueBase pqb; + PktIn *(*after)(PacketQueueBase *, PacketQueueNode *prev, bool pop); +} PktInQueue; + +typedef struct PktOutQueue { + PacketQueueBase pqb; + PktOut *(*after)(PacketQueueBase *, PacketQueueNode *prev, bool pop); +} PktOutQueue; + +void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node); +void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node); +void pq_base_concatenate(PacketQueueBase *dest, + PacketQueueBase *q1, PacketQueueBase *q2); + +void pq_in_init(PktInQueue *pq); +void pq_out_init(PktOutQueue *pq); +void pq_in_clear(PktInQueue *pq); +void pq_out_clear(PktOutQueue *pq); + +#define pq_push(pq, pkt) \ + TYPECHECK((pq)->after(&(pq)->pqb, NULL, false) == pkt, \ + pq_base_push(&(pq)->pqb, &(pkt)->qnode)) +#define pq_push_front(pq, pkt) \ + TYPECHECK((pq)->after(&(pq)->pqb, NULL, false) == pkt, \ + pq_base_push_front(&(pq)->pqb, &(pkt)->qnode)) +#define pq_peek(pq) ((pq)->after(&(pq)->pqb, &(pq)->pqb.end, false)) +#define pq_pop(pq) ((pq)->after(&(pq)->pqb, &(pq)->pqb.end, true)) +#define pq_concatenate(dst, q1, q2) \ + TYPECHECK((q1)->after(&(q1)->pqb, NULL, false) == \ + (dst)->after(&(dst)->pqb, NULL, false) && \ + (q2)->after(&(q2)->pqb, NULL, false) == \ + (dst)->after(&(dst)->pqb, NULL, false), \ + pq_base_concatenate(&(dst)->pqb, &(q1)->pqb, &(q2)->pqb)) + +#define pq_first(pq) pq_peek(pq) +#define pq_next(pq, pkt) ((pq)->after(&(pq)->pqb, &(pkt)->qnode, false)) + +/* + * Packet type contexts, so that ssh2_pkt_type can correctly decode + * the ambiguous type numbers back into the correct type strings. + */ +typedef enum { + SSH2_PKTCTX_NOKEX, + SSH2_PKTCTX_DHGROUP, + SSH2_PKTCTX_DHGEX, + SSH2_PKTCTX_ECDHKEX, + SSH2_PKTCTX_GSSKEX, + SSH2_PKTCTX_RSAKEX +} Pkt_KCtx; +typedef enum { + SSH2_PKTCTX_NOAUTH, + SSH2_PKTCTX_PUBLICKEY, + SSH2_PKTCTX_PASSWORD, + SSH2_PKTCTX_GSSAPI, + SSH2_PKTCTX_KBDINTER +} Pkt_ACtx; + +typedef struct PacketLogSettings { + bool omit_passwords, omit_data; + Pkt_KCtx kctx; + Pkt_ACtx actx; +} PacketLogSettings; + +#define MAX_BLANKS 4 /* no packet needs more censored sections than this */ +int ssh1_censor_packet( + const PacketLogSettings *pls, int type, bool sender_is_client, + ptrlen pkt, logblank_t *blanks); +int ssh2_censor_packet( + const PacketLogSettings *pls, int type, bool sender_is_client, + ptrlen pkt, logblank_t *blanks); + +PktOut *ssh_new_packet(void); +void ssh_free_pktout(PktOut *pkt); + +Socket *ssh_connection_sharing_init( + const char *host, int port, Conf *conf, LogContext *logctx, + Plug *sshplug, ssh_sharing_state **state); +void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, + ConnectionLayer *cl); +bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf); +void share_got_pkt_from_server(ssh_sharing_connstate *ctx, int type, + const void *pkt, int pktlen); +void share_activate(ssh_sharing_state *sharestate, + const char *server_verstring); +void sharestate_free(ssh_sharing_state *state); +int share_ndownstreams(ssh_sharing_state *state); + +void ssh_connshare_log(Ssh *ssh, int event, const char *logtext, const char *ds_err, const char *us_err); -unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx); -void ssh_delete_sharing_channel(Ssh ssh, unsigned localid); -int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport, - void *share_ctx); -void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx); -struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype, - void *share_cs, - void *share_chan); -void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth); -void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type, - const void *pkt, int pktlen, - const char *additional_log_text); -void ssh_sharing_downstream_connected(Ssh ssh, unsigned id, - const char *peerinfo); -void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id); -void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...); -int ssh_agent_forwarding_permitted(Ssh ssh); -void share_setup_x11_channel(void *csv, void *chanv, +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, @@ -58,47 +185,280 @@ void share_setup_x11_channel(void *csv, void *chanv, int protomajor, int protominor, const void *initial_data, int initial_len); -/* - * Useful thing. - */ -#ifndef lenof -#define lenof(x) ( (sizeof((x))) / (sizeof(*(x)))) -#endif +/* Per-application overrides for what roles we can take in connection + * sharing, regardless of user configuration (e.g. pscp will never be + * an upstream) */ +extern const bool share_can_be_downstream; +extern const bool share_can_be_upstream; + +struct X11Display; +struct X11FakeAuth; + +/* Structure definition centralised here because the SSH-1 and SSH-2 + * connection layers both use it. But the client module (portfwd.c) + * should not try to look inside here. */ +struct ssh_rportfwd { + unsigned sport, dport; + char *shost, *dhost; + int addressfamily; + char *log_description; /* name of remote listening port, for logging */ + ssh_sharing_connstate *share_ctx; + PortFwdRecord *pfr; +}; +void free_rportfwd(struct ssh_rportfwd *rpf); + +struct ConnectionLayerVtable { + /* Allocate and free remote-to-local port forwardings, called by + * PortFwdManager or by connection sharing */ + struct ssh_rportfwd *(*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 (*rportfwd_remove)(ConnectionLayer *cl, struct ssh_rportfwd *rpf); + + /* Open a local-to-remote port forwarding channel, called by + * PortFwdManager */ + SshChannel *(*lportfwd_open)( + ConnectionLayer *cl, const char *hostname, int port, + const char *description, const SocketPeerInfo *peerinfo, + Channel *chan); + + /* Initiate opening of a 'session'-type channel */ + SshChannel *(*session_open)(ConnectionLayer *cl, Channel *chan); + + /* Open outgoing channels for X and agent forwarding. (Used in the + * SSH server.) */ + SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan, + const SocketPeerInfo *pi); + SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan); + + /* Add an X11 display for ordinary X forwarding */ + struct X11FakeAuth *(*add_x11_display)( + ConnectionLayer *cl, int authtype, struct X11Display *x11disp); + + /* Add and remove X11 displays for connection sharing downstreams */ + struct X11FakeAuth *(*add_sharing_x11_display)( + ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, + share_channel *share_chan); + void (*remove_sharing_x11_display)( + ConnectionLayer *cl, struct X11FakeAuth *auth); + + /* Pass through an outgoing SSH packet from a downstream */ + void (*send_packet_from_downstream)( + ConnectionLayer *cl, unsigned id, int type, + const void *pkt, int pktlen, const char *additional_log_text); + + /* Allocate/free an upstream channel number associated with a + * sharing downstream */ + unsigned (*alloc_sharing_channel)(ConnectionLayer *cl, + ssh_sharing_connstate *connstate); + void (*delete_sharing_channel)(ConnectionLayer *cl, unsigned localid); + + /* Indicate that a downstream has sent a global request with the + * want-reply flag, so that when a reply arrives it will be passed + * back to that downstrean */ + void (*sharing_queue_global_request)( + ConnectionLayer *cl, ssh_sharing_connstate *connstate); + + /* Indicate that the last downstream has disconnected */ + void (*sharing_no_more_downstreams)(ConnectionLayer *cl); + + /* Query whether the connection layer is doing agent forwarding */ + bool (*agent_forwarding_permitted)(ConnectionLayer *cl); + + /* Set the size of the main terminal window (if any) */ + void (*terminal_size)(ConnectionLayer *cl, int width, int height); + + /* Indicate that the backlog on standard output has cleared */ + void (*stdout_unthrottle)(ConnectionLayer *cl, int bufsize); + + /* Query the size of the backlog on standard _input_ */ + int (*stdin_backlog)(ConnectionLayer *cl); + + /* Tell the connection layer that the SSH connection itself has + * backed up, so it should tell all currently open channels to + * cease reading from their local input sources if they can. (Or + * tell it that that state of affairs has gone away again.) */ + void (*throttle_all_channels)(ConnectionLayer *cl, bool throttled); + + /* Ask the connection layer about its current preference for + * line-discipline options. */ + bool (*ldisc_option)(ConnectionLayer *cl, int option); + + /* Communicate _to_ the connection layer (from the main session + * channel) what its preference for line-discipline options is. */ + void (*set_ldisc_option)(ConnectionLayer *cl, int option, bool value); + + /* Communicate to the connection layer whether X and agent + * forwarding were successfully enabled (for purposes of + * knowing whether to accept subsequent channel-opens). */ + void (*enable_x_fwd)(ConnectionLayer *cl); + void (*enable_agent_fwd)(ConnectionLayer *cl); + + /* Communicate to the connection layer whether the main session + * channel currently wants user input. */ + void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted); +}; + +struct ConnectionLayer { + LogContext *logctx; + const struct ConnectionLayerVtable *vt; +}; + +#define ssh_rportfwd_alloc(cl, sh, sp, dh, dp, af, ld, pfr, share) \ + ((cl)->vt->rportfwd_alloc(cl, sh, sp, dh, dp, af, ld, pfr, share)) +#define ssh_rportfwd_remove(cl, rpf) ((cl)->vt->rportfwd_remove(cl, rpf)) +#define ssh_lportfwd_open(cl, h, p, desc, pi, chan) \ + ((cl)->vt->lportfwd_open(cl, h, p, desc, pi, chan)) +#define ssh_serverside_x11_open(cl, chan, pi) \ + ((cl)->vt->serverside_x11_open(cl, chan, pi)) +#define ssh_serverside_agent_open(cl, chan) \ + ((cl)->vt->serverside_agent_open(cl, chan)) +#define ssh_session_open(cl, chan) \ + ((cl)->vt->session_open(cl, chan)) +#define ssh_add_x11_display(cl, auth, disp) \ + ((cl)->vt->add_x11_display(cl, auth, disp)) +#define ssh_add_sharing_x11_display(cl, auth, cs, ch) \ + ((cl)->vt->add_sharing_x11_display(cl, auth, cs, ch)) +#define ssh_remove_sharing_x11_display(cl, fa) \ + ((cl)->vt->remove_sharing_x11_display(cl, fa)) +#define ssh_send_packet_from_downstream(cl, id, type, pkt, len, log) \ + ((cl)->vt->send_packet_from_downstream(cl, id, type, pkt, len, log)) +#define ssh_alloc_sharing_channel(cl, cs) \ + ((cl)->vt->alloc_sharing_channel(cl, cs)) +#define ssh_delete_sharing_channel(cl, ch) \ + ((cl)->vt->delete_sharing_channel(cl, ch)) +#define ssh_sharing_queue_global_request(cl, cs) \ + ((cl)->vt->sharing_queue_global_request(cl, cs)) +#define ssh_sharing_no_more_downstreams(cl) \ + ((cl)->vt->sharing_no_more_downstreams(cl)) +#define ssh_agent_forwarding_permitted(cl) \ + ((cl)->vt->agent_forwarding_permitted(cl)) +#define ssh_terminal_size(cl, w, h) ((cl)->vt->terminal_size(cl, w, h)) +#define ssh_stdout_unthrottle(cl, bufsize) \ + ((cl)->vt->stdout_unthrottle(cl, bufsize)) +#define ssh_stdin_backlog(cl) ((cl)->vt->stdin_backlog(cl)) +#define ssh_throttle_all_channels(cl, throttled) \ + ((cl)->vt->throttle_all_channels(cl, throttled)) +#define ssh_ldisc_option(cl, option) ((cl)->vt->ldisc_option(cl, option)) +#define ssh_set_ldisc_option(cl, opt, val) \ + ((cl)->vt->set_ldisc_option(cl, opt, val)) +#define ssh_enable_x_fwd(cl) ((cl)->vt->enable_x_fwd(cl)) +#define ssh_enable_agent_fwd(cl) ((cl)->vt->enable_agent_fwd(cl)) +#define ssh_set_wants_user_input(cl, wanted) \ + ((cl)->vt->set_wants_user_input(cl, wanted)) +#define ssh_setup_server_x_forwarding(cl, conf, ap, ad, scr) \ + ((cl)->vt->setup_server_x_forwarding(cl, conf, ap, ad, scr)) + +/* Exports from portfwd.c */ +PortFwdManager *portfwdmgr_new(ConnectionLayer *cl); +void portfwdmgr_free(PortFwdManager *mgr); +void portfwdmgr_config(PortFwdManager *mgr, Conf *conf); +void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr); +void portfwdmgr_close_all(PortFwdManager *mgr); +char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, + char *hostname, int port, SshChannel *c, + int addressfamily); +bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, + const char *keyhost, int keyport, Conf *conf); +bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port); +Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug); +void portfwd_raw_free(Channel *pfchan); +void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc); + +Socket *platform_make_agent_socket(Plug *plug, const char *dirprefix, + char **error, char **name); + +LogContext *ssh_get_logctx(Ssh *ssh); + +/* Communications back to ssh.c from connection layers */ +void ssh_throttle_conn(Ssh *ssh, int adjust); +void ssh_got_exitcode(Ssh *ssh, int status); +void ssh_ldisc_update(Ssh *ssh); +void ssh_got_fallback_cmd(Ssh *ssh); + +/* Functions to abort the connection, for various reasons. */ +void ssh_remote_error(Ssh *ssh, const char *fmt, ...); +void ssh_remote_eof(Ssh *ssh, const char *fmt, ...); +void ssh_proto_error(Ssh *ssh, const char *fmt, ...); +void ssh_sw_abort(Ssh *ssh, const char *fmt, ...); +void ssh_user_close(Ssh *ssh, const char *fmt, ...); #define SSH_CIPHER_IDEA 1 #define SSH_CIPHER_DES 2 #define SSH_CIPHER_3DES 3 #define SSH_CIPHER_BLOWFISH 6 -#ifdef MSCRYPTOAPI -#define APIEXTRA 8 -#else -#define APIEXTRA 0 -#endif - #ifndef BIGNUM_INTERNAL typedef void *Bignum; #endif +typedef struct ssh_keyalg ssh_keyalg; +typedef const struct ssh_keyalg *ssh_key; + struct RSAKey { int bits; int bytes; -#ifdef MSCRYPTOAPI - unsigned long exponent; - unsigned char *modulus; -#else Bignum modulus; Bignum exponent; Bignum private_exponent; Bignum p; Bignum q; Bignum iqmp; -#endif char *comment; + ssh_key sshk; +}; + +struct RSACertKey { + ptrlen certificate; + char* nonce; + Bignum modulus; + Bignum exponent; + uint64_t serial; + uint32_t type; + char * keyid; + char * principals; + uint64_t valid_after; + uint64_t valid_before; + char * options; + char * extensions; + char * reserved; + ssh_key* sigkey; + char * signature; + Bignum private_exponent; + Bignum iqmp; + Bignum p; + Bignum q; + char *comment; + ssh_key sshk; }; struct dss_key { Bignum p, q, g, y, x; + ssh_key sshk; +}; + +struct dss_cert_key { + ptrlen certificate; + char* nonce; + Bignum p; + Bignum q; + Bignum g; + Bignum y; + uint64_t serial; + uint32_t type; + char * keyid; + char * principals; + uint64_t valid_after; + uint64_t valid_before; + char * options; + char * extensions; + char * reserved; + ssh_key* sigkey; + char * signature; + Bignum x; + ssh_key sshk; }; struct ec_curve; @@ -107,10 +467,17 @@ struct ec_point { const struct ec_curve *curve; Bignum x, y; Bignum z; /* Jacobian denominator */ - unsigned char infinity; + bool infinity; }; +/* A couple of ECC functions exported for use outside sshecc.c */ +struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b); void ec_point_free(struct ec_point *point); +bool BinarySource_get_point(BinarySource *src, struct ec_point *point); +#define get_point(src, pt) BinarySource_get_point(BinarySource_UPCAST(src), pt) +bool decodepoint_ed(const char *p, int length, struct ec_point *point); +#define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src)) +Bignum BinarySource_get_mp_le(BinarySource *src); /* Weierstrass form curve */ struct ec_wcurve @@ -119,7 +486,7 @@ struct ec_wcurve struct ec_point G; }; -/* Montgomery form curve */ +/* Montgomery form curvfe */ struct ec_mcurve { Bignum a, b; @@ -152,78 +519,116 @@ struct ec_curve { }; }; -const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid, +struct ecsign_extra { + struct ec_curve *(*curve)(void); + const struct ssh_hashalg *hash; + + /* These fields are used by the OpenSSH PEM format importer/exporter */ + const unsigned char *oid; + int oidlen; +}; + +const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, const struct ec_curve **curve); -const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, int *oidlen); +const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen); extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths; -int ec_nist_alg_and_curve_by_bits(int bits, - const struct ec_curve **curve, - const struct ssh_signkey **alg); -int ec_ed_alg_and_curve_by_bits(int bits, - const struct ec_curve **curve, - const struct ssh_signkey **alg); - -struct ssh_signkey; +bool ec_nist_alg_and_curve_by_bits(int bits, + const struct ec_curve **curve, + const ssh_keyalg **alg); +bool ec_ed_alg_and_curve_by_bits(int bits, + const struct ec_curve **curve, + const ssh_keyalg **alg); struct ec_key { - const struct ssh_signkey *signalg; struct ec_point publicKey; Bignum privateKey; + ssh_key sshk; +}; + +struct ec_cert_key { + ptrlen certificate; + char* nonce; + struct ec_point publicKey; + uint64_t serial; + uint32_t type; + char * keyid; + char * principals; + uint64_t valid_after; + uint64_t valid_before; + char * options; + char * extensions; + char * reserved; + ssh_key* sigkey; + char * signature; + Bignum privateKey; + ssh_key sshk; }; struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve); -int makekey(const unsigned char *data, int len, struct RSAKey *result, - const unsigned char **keystr, int order); -int makeprivate(const unsigned char *data, int len, struct RSAKey *result); -int rsaencrypt(unsigned char *data, int length, struct RSAKey *key); -Bignum rsadecrypt(Bignum input, struct RSAKey *key); -void rsasign(unsigned char *data, int length, struct RSAKey *key); +/* + * SSH-1 never quite decided which order to store the two components + * of an RSA key. During connection setup, the server sends its host + * and server keys with the exponent first; private key files store + * the modulus first. The agent protocol is even more confusing, + * because the client specifies a key to the server in one order and + * the server lists the keys it knows about in the other order! + */ +typedef enum { RSA_SSH1_EXPONENT_FIRST, RSA_SSH1_MODULUS_FIRST } RsaSsh1Order; + +void BinarySource_get_rsa_ssh1_pub( + BinarySource *src, struct RSAKey *result, RsaSsh1Order order); +void BinarySource_get_rsa_ssh1_priv( + BinarySource *src, struct RSAKey *rsa); +bool rsa_ssh1_encrypt(unsigned char *data, int length, struct RSAKey *key); +Bignum rsa_ssh1_decrypt(Bignum input, struct RSAKey *key); +bool rsa_ssh1_decrypt_pkcs1(Bignum input, struct RSAKey *key, strbuf *outbuf); void rsasanitise(struct RSAKey *key); int rsastr_len(struct RSAKey *key); void rsastr_fmt(char *str, struct RSAKey *key); -void rsa_fingerprint(char *str, int len, struct RSAKey *key); -int rsa_verify(struct RSAKey *key); -unsigned char *rsa_public_blob(struct RSAKey *key, int *len); -int rsa_public_blob_len(void *data, int maxlen); +char *rsa_ssh1_fingerprint(struct RSAKey *key); +bool rsa_verify(struct RSAKey *key); +void rsa_ssh1_public_blob(BinarySink *bs, struct RSAKey *key, + RsaSsh1Order order); +int rsa_ssh1_public_blob_len(void *data, int maxlen); void freersakey(struct RSAKey *key); -#ifndef PUTTY_UINT32_DEFINED -/* This makes assumptions about the int type. */ -typedef unsigned int uint32; -#define PUTTY_UINT32_DEFINED -#endif -typedef uint32 word32; - unsigned long crc32_compute(const void *s, size_t len); unsigned long crc32_update(unsigned long crc_input, const void *s, size_t len); /* SSH CRC compensation attack detector */ -void *crcda_make_context(void); -void crcda_free_context(void *handle); -int detect_attack(void *handle, unsigned char *buf, uint32 len, - unsigned char *IV); +struct crcda_ctx; +struct crcda_ctx *crcda_make_context(void); +void crcda_free_context(struct crcda_ctx *ctx); +bool detect_attack(struct crcda_ctx *ctx, unsigned char *buf, uint32_t len, + unsigned char *IV); /* * SSH2 RSA key exchange functions */ -struct ssh_hash; -void *ssh_rsakex_newkey(char *data, int len); -void ssh_rsakex_freekey(void *key); -int ssh_rsakex_klen(void *key); -void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, - unsigned char *out, int outlen, - void *key); +struct ssh_hashalg; +struct ssh_rsa_kex_extra { + int minklen; +}; +struct RSAKey *ssh_rsakex_newkey(const void *data, int len); +void ssh_rsakex_freekey(struct RSAKey *key); +int ssh_rsakex_klen(struct RSAKey *key); +void ssh_rsakex_encrypt(const struct ssh_hashalg *h, + unsigned char *in, int inlen, + unsigned char *out, int outlen, struct RSAKey *key); +Bignum ssh_rsakex_decrypt(const struct ssh_hashalg *h, ptrlen ciphertext, + struct RSAKey *rsa); /* * SSH2 ECDH key exchange functions */ struct ssh_kex; const char *ssh_ecdhkex_curve_textname(const struct ssh_kex *kex); -void *ssh_ecdhkex_newkey(const struct ssh_kex *kex); -void ssh_ecdhkex_freekey(void *key); -char *ssh_ecdhkex_getpublic(void *key, int *len); -Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen); +struct ec_key *ssh_ecdhkex_newkey(const struct ssh_kex *kex); +void ssh_ecdhkex_freekey(struct ec_key *key); +void ssh_ecdhkex_getpublic(struct ec_key *key, BinarySink *bs); +Bignum ssh_ecdhkex_getkey(struct ec_key *key, + const void *remoteKey, int remoteKeyLen); /* * Helper function for k generation in DSA, reused in ECDSA @@ -231,94 +636,109 @@ Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen); Bignum *dss_gen_k(const char *id_string, Bignum modulus, Bignum private_key, unsigned char *digest, int digest_len); +struct ssh2_cipheralg; +typedef const struct ssh2_cipheralg *ssh2_cipher; + typedef struct { - uint32 h[4]; + uint32_t h[4]; } MD5_Core_State; struct MD5Context { -#ifdef MSCRYPTOAPI - unsigned long hHash; -#else MD5_Core_State core; unsigned char block[64]; int blkused; - uint32 lenhi, lenlo; -#endif + uint64_t len; + BinarySink_IMPLEMENTATION; }; void MD5Init(struct MD5Context *context); -void MD5Update(struct MD5Context *context, unsigned char const *buf, - unsigned len); void MD5Final(unsigned char digest[16], struct MD5Context *context); void MD5Simple(void const *p, unsigned len, unsigned char output[16]); -void *hmacmd5_make_context(void *); -void hmacmd5_free_context(void *handle); -void hmacmd5_key(void *handle, void const *key, int len); -void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len, - unsigned char *hmac); +struct hmacmd5_context; +struct hmacmd5_context *hmacmd5_make_context(void); +void hmacmd5_free_context(struct hmacmd5_context *ctx); +void hmacmd5_key(struct hmacmd5_context *ctx, void const *key, int len); +void hmacmd5_do_hmac(struct hmacmd5_context *ctx, + const void *blk, int len, unsigned char *hmac); -typedef struct { - uint32 h[5]; +bool supports_sha_ni(void); + +typedef struct SHA_State { + uint32_t h[5]; unsigned char block[64]; int blkused; - uint32 lenhi, lenlo; + uint64_t len; + void (*sha1)(struct SHA_State * s, const unsigned char *p, int len); + BinarySink_IMPLEMENTATION; } SHA_State; void SHA_Init(SHA_State * s); -void SHA_Bytes(SHA_State * s, const void *p, int len); void SHA_Final(SHA_State * s, unsigned char *output); void SHA_Simple(const void *p, int len, unsigned char *output); -void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, +void hmac_sha1_simple(const void *key, int keylen, + const void *data, int datalen, unsigned char *output); -typedef struct { - uint32 h[8]; +typedef struct SHA256_State { + uint32_t h[8]; unsigned char block[64]; int blkused; - uint32 lenhi, lenlo; + uint64_t len; + void (*sha256)(struct SHA256_State * s, const unsigned char *p, int len); + BinarySink_IMPLEMENTATION; } SHA256_State; void SHA256_Init(SHA256_State * s); -void SHA256_Bytes(SHA256_State * s, const void *p, int len); void SHA256_Final(SHA256_State * s, unsigned char *output); void SHA256_Simple(const void *p, int len, unsigned char *output); typedef struct { - uint64 h[8]; + uint64_t h[8]; unsigned char block[128]; int blkused; - uint32 len[4]; + uint64_t lenhi, lenlo; + BinarySink_IMPLEMENTATION; } SHA512_State; #define SHA384_State SHA512_State void SHA512_Init(SHA512_State * s); -void SHA512_Bytes(SHA512_State * s, const void *p, int len); void SHA512_Final(SHA512_State * s, unsigned char *output); void SHA512_Simple(const void *p, int len, unsigned char *output); void SHA384_Init(SHA384_State * s); -#define SHA384_Bytes(s, p, len) SHA512_Bytes(s, p, len) void SHA384_Final(SHA384_State * s, unsigned char *output); void SHA384_Simple(const void *p, int len, unsigned char *output); -struct ssh_mac; -struct ssh_cipher { - void *(*make_context)(void); - void (*free_context)(void *); - void (*sesskey) (void *, unsigned char *key); /* for SSH-1 */ - void (*encrypt) (void *, unsigned char *blk, int len); - void (*decrypt) (void *, unsigned char *blk, int len); +struct ssh2_macalg; + +struct ssh1_cipheralg; +typedef const struct ssh1_cipheralg *ssh1_cipher; + +struct ssh1_cipheralg { + ssh1_cipher *(*new)(void); + void (*free)(ssh1_cipher *); + void (*sesskey)(ssh1_cipher *, const void *key); + void (*encrypt)(ssh1_cipher *, void *blk, int len); + void (*decrypt)(ssh1_cipher *, void *blk, int len); int blksize; const char *text_name; }; -struct ssh2_cipher { - void *(*make_context)(void); - void (*free_context)(void *); - void (*setiv) (void *, unsigned char *key); /* for SSH-2 */ - void (*setkey) (void *, unsigned char *key);/* for SSH-2 */ - void (*encrypt) (void *, unsigned char *blk, int len); - void (*decrypt) (void *, unsigned char *blk, int len); +#define ssh1_cipher_new(alg) ((alg)->new()) +#define ssh1_cipher_free(ctx) ((*(ctx))->free(ctx)) +#define ssh1_cipher_sesskey(ctx, key) ((*(ctx))->sesskey(ctx, key)) +#define ssh1_cipher_encrypt(ctx, blk, len) ((*(ctx))->encrypt(ctx, blk, len)) +#define ssh1_cipher_decrypt(ctx, blk, len) ((*(ctx))->decrypt(ctx, blk, len)) + +struct ssh2_cipheralg { + ssh2_cipher *(*new)(const struct ssh2_cipheralg *alg); + void (*free)(ssh2_cipher *); + void (*setiv)(ssh2_cipher *, const void *iv); + void (*setkey)(ssh2_cipher *, const void *key); + void (*encrypt)(ssh2_cipher *, void *blk, int len); + void (*decrypt)(ssh2_cipher *, void *blk, int len); /* Ignored unless SSH_CIPHER_SEPARATE_LENGTH flag set */ - void (*encrypt_length) (void *, unsigned char *blk, int len, unsigned long seq); - void (*decrypt_length) (void *, unsigned char *blk, int len, unsigned long seq); + void (*encrypt_length)(ssh2_cipher *, void *blk, int len, + unsigned long seq); + void (*decrypt_length)(ssh2_cipher *, void *blk, int len, + unsigned long seq); const char *name; int blksize; /* real_keybits is the number of bits of entropy genuinely used by @@ -339,46 +759,80 @@ struct ssh2_cipher { #define SSH_CIPHER_SEPARATE_LENGTH 2 const char *text_name; /* If set, this takes priority over other MAC. */ - const struct ssh_mac *required_mac; + const struct ssh2_macalg *required_mac; }; +#define ssh2_cipher_new(alg) ((alg)->new(alg)) +#define ssh2_cipher_free(ctx) ((*(ctx))->free(ctx)) +#define ssh2_cipher_setiv(ctx, iv) ((*(ctx))->setiv(ctx, iv)) +#define ssh2_cipher_setkey(ctx, key) ((*(ctx))->setkey(ctx, key)) +#define ssh2_cipher_encrypt(ctx, blk, len) ((*(ctx))->encrypt(ctx, blk, len)) +#define ssh2_cipher_decrypt(ctx, blk, len) ((*(ctx))->decrypt(ctx, blk, len)) +#define ssh2_cipher_encrypt_length(ctx, blk, len, seq) \ + ((*(ctx))->encrypt_length(ctx, blk, len, seq)) +#define ssh2_cipher_decrypt_length(ctx, blk, len, seq) \ + ((*(ctx))->decrypt_length(ctx, blk, len, seq)) +#define ssh2_cipher_alg(ctx) (*(ctx)) + struct ssh2_ciphers { int nciphers; - const struct ssh2_cipher *const *list; + const struct ssh2_cipheralg *const *list; }; -struct ssh_mac { +struct ssh2_macalg; +typedef struct ssh2_mac { + const struct ssh2_macalg *vt; + BinarySink_DELEGATE_IMPLEMENTATION; +} ssh2_mac; + +struct ssh2_macalg { /* Passes in the cipher context */ - void *(*make_context)(void *); - void (*free_context)(void *); - void (*setkey) (void *, unsigned char *key); - /* whole-packet operations */ - void (*generate) (void *, unsigned char *blk, int len, unsigned long seq); - int (*verify) (void *, unsigned char *blk, int len, unsigned long seq); - /* partial-packet operations */ - void (*start) (void *); - void (*bytes) (void *, unsigned char const *, int); - void (*genresult) (void *, unsigned char *); - int (*verresult) (void *, unsigned char const *); + ssh2_mac *(*new)(const struct ssh2_macalg *alg, ssh2_cipher *cipher); + void (*free)(ssh2_mac *); + void (*setkey)(ssh2_mac *, const void *key); + void (*start)(ssh2_mac *); + void (*genresult)(ssh2_mac *, unsigned char *); const char *name, *etm_name; int len, keylen; const char *text_name; }; -struct ssh_hash { - void *(*init)(void); /* also allocates context */ - void *(*copy)(const void *); - void (*bytes)(void *, const void *, int); - void (*final)(void *, unsigned char *); /* also frees context */ - void (*free)(void *); +#define ssh2_mac_new(alg, cipher) ((alg)->new(alg, cipher)) +#define ssh2_mac_free(ctx) ((ctx)->vt->free(ctx)) +#define ssh2_mac_setkey(ctx, key) ((ctx)->vt->free(ctx, key)) +#define ssh2_mac_start(ctx) ((ctx)->vt->start(ctx)) +#define ssh2_mac_genresult(ctx, out) ((ctx)->vt->genresult(ctx, out)) +#define ssh2_mac_alg(ctx) ((ctx)->vt) + +/* Centralised 'methods' for ssh2_mac, defined in sshmac.c */ +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); + +typedef struct ssh_hash { + const struct ssh_hashalg *vt; + BinarySink_DELEGATE_IMPLEMENTATION; +} ssh_hash; + +struct ssh_hashalg { + ssh_hash *(*new)(const struct ssh_hashalg *alg); + ssh_hash *(*copy)(ssh_hash *); + void (*final)(ssh_hash *, unsigned char *); /* ALSO FREES THE ssh_hash! */ + void (*free)(ssh_hash *); int hlen; /* output length in bytes */ const char *text_name; }; +#define ssh_hash_new(alg) ((alg)->new(alg)) +#define ssh_hash_copy(ctx) ((ctx)->vt->copy(ctx)) +#define ssh_hash_final(ctx, out) ((ctx)->vt->final(ctx, out)) +#define ssh_hash_free(ctx) ((ctx)->vt->free(ctx)) +#define ssh_hash_alg(ctx) ((ctx)->vt) + struct ssh_kex { const char *name, *groupname; - enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH } main_type; - const struct ssh_hash *hash; + enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, KEXTYPE_GSS } main_type; + const struct ssh_hashalg *hash; const void *extra; /* private to the kex methods */ }; @@ -387,104 +841,140 @@ struct ssh_kexes { const struct ssh_kex *const *list; }; -struct ssh_signkey { - void *(*newkey) (const struct ssh_signkey *self, - const char *data, int len); - void (*freekey) (void *key); - char *(*fmtkey) (void *key); - unsigned char *(*public_blob) (void *key, int *len); - unsigned char *(*private_blob) (void *key, int *len); - void *(*createkey) (const struct ssh_signkey *self, - const unsigned char *pub_blob, int pub_len, - const unsigned char *priv_blob, int priv_len); - void *(*openssh_createkey) (const struct ssh_signkey *self, - const unsigned char **blob, int *len); - int (*openssh_fmtkey) (void *key, unsigned char *blob, int len); - /* OpenSSH private key blobs, as created by openssh_fmtkey and - * consumed by openssh_createkey, always (at least so far...) take - * the form of a number of SSH-2 strings / mpints concatenated - * end-to-end. Because the new-style OpenSSH private key format - * stores those blobs without a containing string wrapper, we need - * to know how many strings each one consists of, so that we can - * skip over the right number to find the next key in the file. - * openssh_private_npieces gives that information. */ - int openssh_private_npieces; - int (*pubkey_bits) (const struct ssh_signkey *self, - const void *blob, int len); - int (*verifysig) (void *key, const char *sig, int siglen, - const char *data, int datalen); - unsigned char *(*sign) (void *key, const char *data, int datalen, - int *siglen); - const char *name; - const char *keytype; /* for host key cache */ - const void *extra; /* private to the public key methods */ +struct ssh_keyalg { + /* Constructors that create an ssh_key */ + ssh_key *(*new_pub) (const ssh_keyalg *self, ptrlen pub); + ssh_key *(*new_priv) (const ssh_keyalg *self, ptrlen pub, ptrlen priv); + ssh_key *(*new_priv_openssh) (const ssh_keyalg *self, BinarySource *); + + /* Methods that operate on an existing ssh_key */ + void (*freekey) (ssh_key *key); + void (*sign) (ssh_key *key, const void *data, int datalen, BinarySink *); + bool (*verify) (ssh_key *key, ptrlen sig, ptrlen data); + void (*public_blob)(ssh_key *key, BinarySink *); + void (*private_blob)(ssh_key *key, BinarySink *); + void (*openssh_blob) (ssh_key *key, BinarySink *); + char *(*cache_str) (ssh_key *key); + + /* 'Class methods' that don't deal with an ssh_key at all */ + int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); + + /* 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 */ }; -struct ssh_compress { +#define ssh_key_new_pub(alg, data) ((alg)->new_pub(alg, data)) +#define ssh_key_new_priv(alg, pub, priv) ((alg)->new_priv(alg, pub, priv)) +#define ssh_key_new_priv_openssh(alg, bs) ((alg)->new_priv_openssh(alg, bs)) + +#define ssh_key_free(key) ((*(key))->freekey(key)) +#define ssh_key_sign(key, data, len, bs) ((*(key))->sign(key, data, len, bs)) +#define ssh_key_verify(key, sig, data) ((*(key))->verify(key, sig, data)) +#define ssh_key_public_blob(key, bs) ((*(key))->public_blob(key, bs)) +#define ssh_key_private_blob(key, bs) ((*(key))->private_blob(key, bs)) +#define ssh_key_openssh_blob(key, bs) ((*(key))->openssh_blob(key, bs)) +#define ssh_key_cache_str(key) ((*(key))->cache_str(key)) + +#define ssh_key_public_bits(alg, blob) ((alg)->pubkey_bits(alg, blob)) + +#define ssh_key_alg(key) (*(key)) +#define ssh_key_ssh_id(key) ((*(key))->ssh_id) +#define ssh_key_cache_id(key) ((*(key))->cache_id) + +typedef struct ssh_compressor { + const struct ssh_compression_alg *vt; +} ssh_compressor; +typedef struct ssh_decompressor { + const struct ssh_compression_alg *vt; +} ssh_decompressor; + +struct ssh_compression_alg { const char *name; /* For zlib@openssh.com: if non-NULL, this name will be considered once * userauth has completed successfully. */ const char *delayed_name; - void *(*compress_init) (void); - void (*compress_cleanup) (void *); - int (*compress) (void *, unsigned char *block, int len, - unsigned char **outblock, int *outlen); - void *(*decompress_init) (void); - void (*decompress_cleanup) (void *); - int (*decompress) (void *, unsigned char *block, int len, - unsigned char **outblock, int *outlen); - int (*disable_compression) (void *); + ssh_compressor *(*compress_new)(void); + void (*compress_free)(ssh_compressor *); + void (*compress)(ssh_compressor *, unsigned char *block, int len, + unsigned char **outblock, int *outlen, + int minlen); + ssh_decompressor *(*decompress_new)(void); + void (*decompress_free)(ssh_decompressor *); + bool (*decompress)(ssh_decompressor *, unsigned char *block, int len, + unsigned char **outblock, int *outlen); const char *text_name; }; +#define ssh_compressor_new(alg) ((alg)->compress_new()) +#define ssh_compressor_free(comp) ((comp)->vt->compress_free(comp)) +#define ssh_compressor_compress(comp, in, inlen, out, outlen, minlen) \ + ((comp)->vt->compress(comp, in, inlen, out, outlen, minlen)) +#define ssh_compressor_alg(comp) ((comp)->vt) +#define ssh_decompressor_new(alg) ((alg)->decompress_new()) +#define ssh_decompressor_free(comp) ((comp)->vt->decompress_free(comp)) +#define ssh_decompressor_decompress(comp, in, inlen, out, outlen) \ + ((comp)->vt->decompress(comp, in, inlen, out, outlen)) +#define ssh_decompressor_alg(comp) ((comp)->vt) + struct ssh2_userkey { - const struct ssh_signkey *alg; /* the key algorithm */ - void *data; /* the key data */ + ssh_key *key; /* the key itself */ char *comment; /* the key comment */ }; /* The maximum length of any hash algorithm used in kex. (bytes) */ #define SSH2_KEX_MAX_HASH_LEN (64) /* SHA-512 */ -extern const struct ssh_cipher ssh_3des; -extern const struct ssh_cipher ssh_des; -extern const struct ssh_cipher ssh_blowfish_ssh1; +extern const struct ssh1_cipheralg ssh1_3des; +extern const struct ssh1_cipheralg ssh1_des; +extern const struct ssh1_cipheralg ssh1_blowfish; extern const struct ssh2_ciphers ssh2_3des; extern const struct ssh2_ciphers ssh2_des; extern const struct ssh2_ciphers ssh2_aes; extern const struct ssh2_ciphers ssh2_blowfish; extern const struct ssh2_ciphers ssh2_arcfour; extern const struct ssh2_ciphers ssh2_ccp; -extern const struct ssh_hash ssh_sha1; -extern const struct ssh_hash ssh_sha256; -extern const struct ssh_hash ssh_sha384; -extern const struct ssh_hash ssh_sha512; +extern const struct ssh_hashalg ssh_sha1; +extern const struct ssh_hashalg ssh_sha256; +extern const struct ssh_hashalg ssh_sha384; +extern const struct ssh_hashalg ssh_sha512; extern const struct ssh_kexes ssh_diffiehellman_group1; extern const struct ssh_kexes ssh_diffiehellman_group14; extern const struct ssh_kexes ssh_diffiehellman_gex; +extern const struct ssh_kexes ssh_gssk5_sha1_kex; extern const struct ssh_kexes ssh_rsa_kex; extern const struct ssh_kexes ssh_ecdh_kex; -extern const struct ssh_signkey ssh_dss; -extern const struct ssh_signkey ssh_rsa; -extern const struct ssh_signkey ssh_ecdsa_ed25519; -extern const struct ssh_signkey ssh_ecdsa_nistp256; -extern const struct ssh_signkey ssh_ecdsa_nistp384; -extern const struct ssh_signkey ssh_ecdsa_nistp521; -extern const struct ssh_mac ssh_hmac_md5; -extern const struct ssh_mac ssh_hmac_sha1; -extern const struct ssh_mac ssh_hmac_sha1_buggy; -extern const struct ssh_mac ssh_hmac_sha1_96; -extern const struct ssh_mac ssh_hmac_sha1_96_buggy; -extern const struct ssh_mac ssh_hmac_sha256; - -void *aes_make_context(void); -void aes_free_context(void *handle); -void aes128_key(void *handle, unsigned char *key); -void aes192_key(void *handle, unsigned char *key); -void aes256_key(void *handle, unsigned char *key); -void aes_iv(void *handle, unsigned char *iv); -void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len); -void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len); +extern const ssh_keyalg ssh_dss; +extern const ssh_keyalg ssh_rsa; +extern const ssh_keyalg ssh_ecdsa_ed25519; +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 ssh_cert_rsa; +extern const ssh_keyalg ssh_cert_dss; +extern const ssh_keyalg ssh_ecdsa_cert_nistp256; +extern const ssh_keyalg ssh_ecdsa_cert_nistp384; +extern const ssh_keyalg ssh_ecdsa_cert_nistp521; +extern const ssh_keyalg ssh_ecdsa_cert_ed25519; +extern const struct ssh2_macalg ssh_hmac_md5; +extern const struct ssh2_macalg ssh_hmac_sha1; +extern const struct ssh2_macalg ssh_hmac_sha1_buggy; +extern const struct ssh2_macalg ssh_hmac_sha1_96; +extern const struct ssh2_macalg ssh_hmac_sha1_96_buggy; +extern const struct ssh2_macalg ssh_hmac_sha256; +extern const struct ssh_compression_alg ssh_zlib; + +typedef struct AESContext AESContext; +AESContext *aes_make_context(void); +void aes_free_context(AESContext *ctx); +void aes128_key(AESContext *ctx, const void *key); +void aes192_key(AESContext *ctx, const void *key); +void aes256_key(AESContext *ctx, const void *key); +void aes_iv(AESContext *ctx, const void *iv); +void aes_ssh2_encrypt_blk(AESContext *ctx, void *blk, int len); +void aes_ssh2_decrypt_blk(AESContext *ctx, void *blk, int len); +void aes_ssh2_sdctr(AESContext *ctx, void *blk, int len); /* * PuTTY version number formatted as an SSH version string. @@ -496,48 +986,44 @@ extern const char sshver[]; * that fails. This variable is the means by which scp.c can reach * into the SSH code and find out which one it got. */ -extern int ssh_fallback_cmd(void *handle); +extern bool ssh_fallback_cmd(Backend *backend); -#ifndef MSCRYPTOAPI -void SHATransform(word32 * digest, word32 * data); +void SHATransform(uint32_t *digest, uint32_t *data); + +/* + * Check of compiler version + */ +#ifdef _FORCE_SHA_NI +# define COMPILER_SUPPORTS_SHA_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && (defined(__x86_64__) || defined(__i386)) +# define COMPILER_SUPPORTS_SHA_NI +# endif +#elif defined(__GNUC__) +# if ((__GNUC__ >= 5) && (defined(__x86_64__) || defined(__i386))) +# define COMPILER_SUPPORTS_SHA_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_VER >= 1900 +# define COMPILER_SUPPORTS_SHA_NI +# endif +#endif + +#ifdef _FORCE_SOFTWARE_SHA +# undef COMPILER_SUPPORTS_SHA_NI #endif int random_byte(void); void random_add_noise(void *noise, int length); void random_add_heavynoise(void *noise, int length); -void logevent(void *, const char *); - -struct PortForwarding; - -/* Allocate and register a new channel for port forwarding */ -void *new_sock_channel(void *handle, struct PortForwarding *pf); -void ssh_send_port_open(void *channel, const char *hostname, int port, - const char *org); - -/* Exports from portfwd.c */ -extern char *pfd_connect(struct PortForwarding **pf, char *hostname, int port, - void *c, Conf *conf, int addressfamily); -extern void pfd_close(struct PortForwarding *); -extern int pfd_send(struct PortForwarding *, char *data, int len); -extern void pfd_send_eof(struct PortForwarding *); -extern void pfd_confirm(struct PortForwarding *); -extern void pfd_unthrottle(struct PortForwarding *); -extern void pfd_override_throttle(struct PortForwarding *, int enable); -struct PortListener; -/* desthost == NULL indicates dynamic (SOCKS) port forwarding */ -extern char *pfl_listen(char *desthost, int destport, char *srcaddr, - int port, void *backhandle, Conf *conf, - struct PortListener **pl, int address_family); -extern void pfl_terminate(struct PortListener *); - /* Exports from x11fwd.c */ enum { X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 }; struct X11Display { /* Broken-down components of the display name itself */ - int unixdomain; + bool unixdomain; char *hostname; int displaynum; int screennum; @@ -546,7 +1032,7 @@ struct X11Display { /* PuTTY networking SockAddr to connect to the display, and associated * gubbins */ - SockAddr addr; + SockAddr *addr; int port; char *realhost; @@ -578,7 +1064,8 @@ struct X11FakeAuth { * What to do with an X connection matching this auth data. */ struct X11Display *disp; - void *share_cs, *share_chan; + 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, @@ -591,25 +1078,25 @@ int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */ * the supplied authtype parameter configures the preferred * authorisation protocol to use at the remote end. The local auth * details are looked up by calling platform_get_x11_auth. + * + * If the returned pointer is NULL, then *error_msg will contain a + * dynamically allocated error message string. */ -extern struct X11Display *x11_setup_display(const char *display, Conf *); +extern struct X11Display *x11_setup_display(const char *display, Conf *, + char **error_msg); void x11_free_display(struct X11Display *disp); struct X11FakeAuth *x11_invent_fake_auth(tree234 *t, int authtype); void x11_free_fake_auth(struct X11FakeAuth *auth); -struct X11Connection; /* opaque outside x11fwd.c */ -struct X11Connection *x11_init(tree234 *authtree, void *, const char *, int); -extern void x11_close(struct X11Connection *); -extern int x11_send(struct X11Connection *, char *, int); -extern void x11_send_eof(struct X11Connection *s); -extern void x11_unthrottle(struct X11Connection *s); -extern void x11_override_throttle(struct X11Connection *s, int enable); +Channel *x11_new_channel(tree234 *authtree, SshChannel *c, + const char *peeraddr, int peerport, + bool connection_sharing_possible); char *x11_display(const char *display); /* Platform-dependent X11 functions */ extern void platform_get_x11_auth(struct X11Display *display, Conf *); /* examine a mostly-filled-in X11Display and fill in localauth* */ -extern const int platform_uses_x11_unix_by_default; +extern const bool platform_uses_x11_unix_by_default; /* choose default X transport in the absence of a specified one */ -SockAddr platform_get_x11_unix_address(const char *path, int displaynum); +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 */ @@ -629,8 +1116,13 @@ char *platform_get_x_display(void); */ void x11_get_auth_from_authfile(struct X11Display *display, const char *authfilename); -int x11_identify_auth_proto(const char *proto); -void *x11_dehexify(const char *hex, int *outlen); +void x11_format_auth_for_authfile( + BinarySink *bs, SockAddr *addr, int display_no, + ptrlen authproto, ptrlen authdata); +int x11_identify_auth_proto(ptrlen protoname); +void *x11_dehexify(ptrlen hex, int *outlen); + +Channel *agentf_new(SshChannel *c); Bignum copybn(Bignum b); Bignum bn_power_2(int n); @@ -642,17 +1134,13 @@ Bignum modmul(Bignum a, Bignum b, Bignum mod); Bignum modsub(const Bignum a, const Bignum b, const Bignum n); void decbn(Bignum n); extern Bignum Zero, One; -Bignum bignum_from_bytes(const unsigned char *data, int nbytes); -Bignum bignum_from_bytes_le(const unsigned char *data, int nbytes); +Bignum bignum_from_bytes(const void *data, int nbytes); +Bignum bignum_from_bytes_le(const void *data, int nbytes); Bignum bignum_random_in_range(const Bignum lower, const Bignum upper); -int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result); int bignum_bitcount(Bignum bn); -int ssh1_bignum_length(Bignum bn); -int ssh2_bignum_length(Bignum bn); int bignum_byte(Bignum bn, int i); int bignum_bit(Bignum bn, int i); void bignum_set_bit(Bignum bn, int i, int value); -int ssh1_write_bignum(void *data, Bignum bn); Bignum biggcd(Bignum a, Bignum b); unsigned short bignum_mod_short(Bignum number, unsigned short modulus); Bignum bignum_add_long(Bignum number, unsigned long addend); @@ -670,25 +1158,31 @@ int bignum_cmp(Bignum a, Bignum b); char *bignum_decimal(Bignum x); Bignum bignum_from_decimal(const char *decimal); +void BinarySink_put_mp_ssh1(BinarySink *, Bignum); +void BinarySink_put_mp_ssh2(BinarySink *, Bignum); +Bignum BinarySource_get_mp_ssh1(BinarySource *); +Bignum BinarySource_get_mp_ssh2(BinarySource *); + #ifdef DEBUG void diagbn(char *prefix, Bignum md); #endif -int dh_is_gex(const struct ssh_kex *kex); -void *dh_setup_group(const struct ssh_kex *kex); -void *dh_setup_gex(Bignum pval, Bignum gval); -void dh_cleanup(void *); -Bignum dh_create_e(void *, int nbits); -const char *dh_validate_f(void *handle, Bignum f); -Bignum dh_find_K(void *, Bignum f); - -int loadrsakey(const Filename *filename, struct RSAKey *key, - const char *passphrase, const char **errorstr); -int rsakey_encrypted(const Filename *filename, char **comment); -int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, - char **commentptr, const char **errorstr); - -int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase); +bool dh_is_gex(const struct ssh_kex *kex); +struct dh_ctx; +struct dh_ctx *dh_setup_group(const struct ssh_kex *kex); +struct dh_ctx *dh_setup_gex(Bignum pval, Bignum gval); +void dh_cleanup(struct dh_ctx *); +Bignum dh_create_e(struct dh_ctx *, int nbits); +const char *dh_validate_f(struct dh_ctx *, Bignum f); +Bignum dh_find_K(struct dh_ctx *, Bignum f); + +bool rsa_ssh1_encrypted(const Filename *filename, char **comment); +int rsa_ssh1_loadpub(const Filename *filename, BinarySink *bs, + char **commentptr, const char **errorstr); +int rsa_ssh1_loadkey(const Filename *filename, struct RSAKey *key, + const char *passphrase, const char **errorstr); +bool rsa_ssh1_savekey(const Filename *filename, struct RSAKey *key, + char *passphrase); extern int base64_decode_atom(const char *atom, unsigned char *out); extern int base64_lines(int datalen); @@ -700,17 +1194,20 @@ extern void base64_encode(FILE *fp, const unsigned char *data, int datalen, extern struct ssh2_userkey ssh2_wrong_passphrase; #define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase) -int ssh2_userkey_encrypted(const Filename *filename, char **comment); +bool ssh2_userkey_encrypted(const Filename *filename, char **comment); struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, const char *passphrase, const char **errorstr); -unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, - int *pub_blob_len, char **commentptr, - const char **errorstr); -int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, - char *passphrase); -const struct ssh_signkey *find_pubkey_alg(const char *name); -const struct ssh_signkey *find_pubkey_alg_len(int namelen, const char *name); +bool ssh2_userkey_loadpub(const Filename *filename, char **algorithm, + BinarySink *bs, + char **commentptr, const char **errorstr); +bool ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, + char *passphrase); +bool openssh_loadpub(FILE *fp, char **algorithm, + BinarySink *bs, + char **commentptr, const char **errorstr); +const ssh_keyalg *find_pubkey_alg(const char *name); +const ssh_keyalg *find_pubkey_alg_len(ptrlen name); enum { SSH_KEYTYPE_UNOPENABLE, @@ -762,37 +1259,33 @@ void ssh2_write_pubkey(FILE *fp, const char *comment, const void *v_pub_blob, int pub_len, int keytype); char *ssh2_fingerprint_blob(const void *blob, int bloblen); -char *ssh2_fingerprint(const struct ssh_signkey *alg, void *data); +char *ssh2_fingerprint(ssh_key *key); int key_type(const Filename *filename); const char *key_type_to_str(int type); -int import_possible(int type); +bool import_possible(int type); int import_target_type(int type); -int import_encrypted(const Filename *filename, int type, char **comment); +bool import_encrypted(const Filename *filename, int type, char **comment); int import_ssh1(const Filename *filename, int type, struct RSAKey *key, char *passphrase, const char **errmsg_p); struct ssh2_userkey *import_ssh2(const Filename *filename, int type, char *passphrase, const char **errmsg_p); -int export_ssh1(const Filename *filename, int type, - struct RSAKey *key, char *passphrase); -int export_ssh2(const Filename *filename, int type, - struct ssh2_userkey *key, char *passphrase); - -void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len); -void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len); -void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, - unsigned char *blk, int len); -void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, - unsigned char *blk, int len); -void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, - int len); -void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, - int len); - -void des_encrypt_xdmauth(const unsigned char *key, - unsigned char *blk, int len); -void des_decrypt_xdmauth(const unsigned char *key, - unsigned char *blk, int len); +bool export_ssh1(const Filename *filename, int type, + struct RSAKey *key, char *passphrase); +bool export_ssh2(const Filename *filename, int type, + struct ssh2_userkey *key, char *passphrase); + +void des3_decrypt_pubkey(const void *key, void *blk, int len); +void des3_encrypt_pubkey(const void *key, void *blk, int len); +void des3_decrypt_pubkey_ossh(const void *key, const void *iv, + void *blk, int len); +void des3_encrypt_pubkey_ossh(const void *key, const void *iv, + void *blk, int len); +void aes256_encrypt_pubkey(const void *key, void *blk, int len); +void aes256_decrypt_pubkey(const void *key, void *blk, int len); + +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, @@ -821,19 +1314,6 @@ Bignum primegen(int bits, int modulus, int residue, Bignum factor, int phase, progfn_t pfn, void *pfnparam, unsigned firstbits); void invent_firstbits(unsigned *one, unsigned *two); - -/* - * zlib compression. - */ -void *zlib_compress_init(void); -void zlib_compress_cleanup(void *); -void *zlib_decompress_init(void); -void zlib_decompress_cleanup(void *); -int zlib_compress_block(void *, unsigned char *block, int len, - unsigned char **outblock, int *outlen); -int zlib_decompress_block(void *, unsigned char *block, int len, - unsigned char **outblock, int *outlen); - /* * Connection-sharing API provided by platforms. This function must * either: @@ -845,55 +1325,57 @@ int zlib_decompress_block(void *, unsigned char *block, int len, */ enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM }; int platform_ssh_share(const char *name, Conf *conf, - Plug downplug, Plug upplug, Socket *sock, + Plug *downplug, Plug *upplug, Socket **sock, char **logtext, char **ds_err, char **us_err, - int can_upstream, int can_downstream); + bool can_upstream, bool can_downstream); void platform_ssh_share_cleanup(const char *name); /* - * SSH-1 message type codes. + * List macro defining the SSH-1 message type codes. */ -#define SSH1_MSG_DISCONNECT 1 /* 0x1 */ -#define SSH1_SMSG_PUBLIC_KEY 2 /* 0x2 */ -#define SSH1_CMSG_SESSION_KEY 3 /* 0x3 */ -#define SSH1_CMSG_USER 4 /* 0x4 */ -#define SSH1_CMSG_AUTH_RSA 6 /* 0x6 */ -#define SSH1_SMSG_AUTH_RSA_CHALLENGE 7 /* 0x7 */ -#define SSH1_CMSG_AUTH_RSA_RESPONSE 8 /* 0x8 */ -#define SSH1_CMSG_AUTH_PASSWORD 9 /* 0x9 */ -#define SSH1_CMSG_REQUEST_PTY 10 /* 0xa */ -#define SSH1_CMSG_WINDOW_SIZE 11 /* 0xb */ -#define SSH1_CMSG_EXEC_SHELL 12 /* 0xc */ -#define SSH1_CMSG_EXEC_CMD 13 /* 0xd */ -#define SSH1_SMSG_SUCCESS 14 /* 0xe */ -#define SSH1_SMSG_FAILURE 15 /* 0xf */ -#define SSH1_CMSG_STDIN_DATA 16 /* 0x10 */ -#define SSH1_SMSG_STDOUT_DATA 17 /* 0x11 */ -#define SSH1_SMSG_STDERR_DATA 18 /* 0x12 */ -#define SSH1_CMSG_EOF 19 /* 0x13 */ -#define SSH1_SMSG_EXIT_STATUS 20 /* 0x14 */ -#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION 21 /* 0x15 */ -#define SSH1_MSG_CHANNEL_OPEN_FAILURE 22 /* 0x16 */ -#define SSH1_MSG_CHANNEL_DATA 23 /* 0x17 */ -#define SSH1_MSG_CHANNEL_CLOSE 24 /* 0x18 */ -#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25 /* 0x19 */ -#define SSH1_SMSG_X11_OPEN 27 /* 0x1b */ -#define SSH1_CMSG_PORT_FORWARD_REQUEST 28 /* 0x1c */ -#define SSH1_MSG_PORT_OPEN 29 /* 0x1d */ -#define SSH1_CMSG_AGENT_REQUEST_FORWARDING 30 /* 0x1e */ -#define SSH1_SMSG_AGENT_OPEN 31 /* 0x1f */ -#define SSH1_MSG_IGNORE 32 /* 0x20 */ -#define SSH1_CMSG_EXIT_CONFIRMATION 33 /* 0x21 */ -#define SSH1_CMSG_X11_REQUEST_FORWARDING 34 /* 0x22 */ -#define SSH1_CMSG_AUTH_RHOSTS_RSA 35 /* 0x23 */ -#define SSH1_MSG_DEBUG 36 /* 0x24 */ -#define SSH1_CMSG_REQUEST_COMPRESSION 37 /* 0x25 */ -#define SSH1_CMSG_AUTH_TIS 39 /* 0x27 */ -#define SSH1_SMSG_AUTH_TIS_CHALLENGE 40 /* 0x28 */ -#define SSH1_CMSG_AUTH_TIS_RESPONSE 41 /* 0x29 */ -#define SSH1_CMSG_AUTH_CCARD 70 /* 0x46 */ -#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */ -#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */ +#define SSH1_MESSAGE_TYPES(X, y) \ + X(y, SSH1_MSG_DISCONNECT, 1) \ + X(y, SSH1_SMSG_PUBLIC_KEY, 2) \ + X(y, SSH1_CMSG_SESSION_KEY, 3) \ + X(y, SSH1_CMSG_USER, 4) \ + X(y, SSH1_CMSG_AUTH_RSA, 6) \ + X(y, SSH1_SMSG_AUTH_RSA_CHALLENGE, 7) \ + X(y, SSH1_CMSG_AUTH_RSA_RESPONSE, 8) \ + X(y, SSH1_CMSG_AUTH_PASSWORD, 9) \ + X(y, SSH1_CMSG_REQUEST_PTY, 10) \ + X(y, SSH1_CMSG_WINDOW_SIZE, 11) \ + X(y, SSH1_CMSG_EXEC_SHELL, 12) \ + X(y, SSH1_CMSG_EXEC_CMD, 13) \ + X(y, SSH1_SMSG_SUCCESS, 14) \ + X(y, SSH1_SMSG_FAILURE, 15) \ + X(y, SSH1_CMSG_STDIN_DATA, 16) \ + X(y, SSH1_SMSG_STDOUT_DATA, 17) \ + X(y, SSH1_SMSG_STDERR_DATA, 18) \ + X(y, SSH1_CMSG_EOF, 19) \ + X(y, SSH1_SMSG_EXIT_STATUS, 20) \ + X(y, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, 21) \ + X(y, SSH1_MSG_CHANNEL_OPEN_FAILURE, 22) \ + X(y, SSH1_MSG_CHANNEL_DATA, 23) \ + X(y, SSH1_MSG_CHANNEL_CLOSE, 24) \ + X(y, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, 25) \ + X(y, SSH1_SMSG_X11_OPEN, 27) \ + X(y, SSH1_CMSG_PORT_FORWARD_REQUEST, 28) \ + X(y, SSH1_MSG_PORT_OPEN, 29) \ + X(y, SSH1_CMSG_AGENT_REQUEST_FORWARDING, 30) \ + X(y, SSH1_SMSG_AGENT_OPEN, 31) \ + X(y, SSH1_MSG_IGNORE, 32) \ + X(y, SSH1_CMSG_EXIT_CONFIRMATION, 33) \ + X(y, SSH1_CMSG_X11_REQUEST_FORWARDING, 34) \ + X(y, SSH1_CMSG_AUTH_RHOSTS_RSA, 35) \ + X(y, SSH1_MSG_DEBUG, 36) \ + X(y, SSH1_CMSG_REQUEST_COMPRESSION, 37) \ + X(y, SSH1_CMSG_AUTH_TIS, 39) \ + X(y, SSH1_SMSG_AUTH_TIS_CHALLENGE, 40) \ + X(y, SSH1_CMSG_AUTH_TIS_RESPONSE, 41) \ + X(y, SSH1_CMSG_AUTH_CCARD, 70) \ + X(y, SSH1_SMSG_AUTH_CCARD_CHALLENGE, 71) \ + X(y, SSH1_CMSG_AUTH_CCARD_RESPONSE, 72) \ + /* end of list */ #define SSH1_AUTH_RHOSTS 1 /* 0x1 */ #define SSH1_AUTH_RSA 2 /* 0x2 */ @@ -907,58 +1389,79 @@ void platform_ssh_share_cleanup(const char *name); #define SSH1_PROTOFLAGS_SUPPORTED 0 /* 0x1 */ /* - * SSH-2 message type codes. + * List macro defining SSH-2 message type codes. Some of these depend + * on particular contexts (i.e. a previously negotiated kex or auth + * method) */ -#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ -#define SSH2_MSG_IGNORE 2 /* 0x2 */ -#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ -#define SSH2_MSG_DEBUG 4 /* 0x4 */ -#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ -#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ -#define SSH2_MSG_KEXINIT 20 /* 0x14 */ -#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ -#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ -#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ -#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 /* 0x1e */ -#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 /* 0x22 */ -#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ -#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ -#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ -#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ -#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ -#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ -#define SSH2_MSG_KEX_ECDH_INIT 30 /* 0x1e */ -#define SSH2_MSG_KEX_ECDH_REPLY 31 /* 0x1f */ -#define SSH2_MSG_KEX_ECMQV_INIT 30 /* 0x1e */ -#define SSH2_MSG_KEX_ECMQV_REPLY 31 /* 0x1f */ -#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ -#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ -#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ -#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ -#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ -#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ -#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ -#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ -#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ -#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ -#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ -#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ -#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ -#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ -#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ -#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ -#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ -#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ -#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ -#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ -#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ -#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ -#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 -#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 -#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 -#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 -#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 -#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 +#define SSH2_MESSAGE_TYPES(X, K, A, y) \ + X(y, SSH2_MSG_DISCONNECT, 1) \ + X(y, SSH2_MSG_IGNORE, 2) \ + X(y, SSH2_MSG_UNIMPLEMENTED, 3) \ + X(y, SSH2_MSG_DEBUG, 4) \ + X(y, SSH2_MSG_SERVICE_REQUEST, 5) \ + X(y, SSH2_MSG_SERVICE_ACCEPT, 6) \ + X(y, SSH2_MSG_KEXINIT, 20) \ + X(y, SSH2_MSG_NEWKEYS, 21) \ + K(y, SSH2_MSG_KEXDH_INIT, 30, SSH2_PKTCTX_DHGROUP) \ + K(y, SSH2_MSG_KEXDH_REPLY, 31, SSH2_PKTCTX_DHGROUP) \ + K(y, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, 30, SSH2_PKTCTX_DHGEX) \ + K(y, SSH2_MSG_KEX_DH_GEX_REQUEST, 34, SSH2_PKTCTX_DHGEX) \ + K(y, SSH2_MSG_KEX_DH_GEX_GROUP, 31, SSH2_PKTCTX_DHGEX) \ + K(y, SSH2_MSG_KEX_DH_GEX_INIT, 32, SSH2_PKTCTX_DHGEX) \ + K(y, SSH2_MSG_KEX_DH_GEX_REPLY, 33, SSH2_PKTCTX_DHGEX) \ + K(y, SSH2_MSG_KEXGSS_INIT, 30, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXGSS_CONTINUE, 31, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXGSS_COMPLETE, 32, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXGSS_HOSTKEY, 33, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXGSS_ERROR, 34, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXGSS_GROUPREQ, 40, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXGSS_GROUP, 41, SSH2_PKTCTX_GSSKEX) \ + K(y, SSH2_MSG_KEXRSA_PUBKEY, 30, SSH2_PKTCTX_RSAKEX) \ + K(y, SSH2_MSG_KEXRSA_SECRET, 31, SSH2_PKTCTX_RSAKEX) \ + K(y, SSH2_MSG_KEXRSA_DONE, 32, SSH2_PKTCTX_RSAKEX) \ + K(y, SSH2_MSG_KEX_ECDH_INIT, 30, SSH2_PKTCTX_ECDHKEX) \ + K(y, SSH2_MSG_KEX_ECDH_REPLY, 31, SSH2_PKTCTX_ECDHKEX) \ + X(y, SSH2_MSG_USERAUTH_REQUEST, 50) \ + X(y, SSH2_MSG_USERAUTH_FAILURE, 51) \ + X(y, SSH2_MSG_USERAUTH_SUCCESS, 52) \ + X(y, SSH2_MSG_USERAUTH_BANNER, 53) \ + A(y, SSH2_MSG_USERAUTH_PK_OK, 60, SSH2_PKTCTX_PUBLICKEY) \ + A(y, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, 60, SSH2_PKTCTX_PASSWORD) \ + A(y, SSH2_MSG_USERAUTH_INFO_REQUEST, 60, SSH2_PKTCTX_KBDINTER) \ + A(y, SSH2_MSG_USERAUTH_INFO_RESPONSE, 61, SSH2_PKTCTX_KBDINTER) \ + A(y, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, 60, SSH2_PKTCTX_GSSAPI) \ + A(y, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, 61, SSH2_PKTCTX_GSSAPI) \ + A(y, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, 63, SSH2_PKTCTX_GSSAPI) \ + A(y, SSH2_MSG_USERAUTH_GSSAPI_ERROR, 64, SSH2_PKTCTX_GSSAPI) \ + A(y, SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, 65, SSH2_PKTCTX_GSSAPI) \ + A(y, SSH2_MSG_USERAUTH_GSSAPI_MIC, 66, SSH2_PKTCTX_GSSAPI) \ + X(y, SSH2_MSG_GLOBAL_REQUEST, 80) \ + X(y, SSH2_MSG_REQUEST_SUCCESS, 81) \ + X(y, SSH2_MSG_REQUEST_FAILURE, 82) \ + X(y, SSH2_MSG_CHANNEL_OPEN, 90) \ + X(y, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, 91) \ + X(y, SSH2_MSG_CHANNEL_OPEN_FAILURE, 92) \ + X(y, SSH2_MSG_CHANNEL_WINDOW_ADJUST, 93) \ + X(y, SSH2_MSG_CHANNEL_DATA, 94) \ + X(y, SSH2_MSG_CHANNEL_EXTENDED_DATA, 95) \ + X(y, SSH2_MSG_CHANNEL_EOF, 96) \ + X(y, SSH2_MSG_CHANNEL_CLOSE, 97) \ + X(y, SSH2_MSG_CHANNEL_REQUEST, 98) \ + X(y, SSH2_MSG_CHANNEL_SUCCESS, 99) \ + X(y, SSH2_MSG_CHANNEL_FAILURE, 100) \ + /* end of list */ + +#define DEF_ENUM_UNIVERSAL(y, name, value) name = value, +#define DEF_ENUM_CONTEXTUAL(y, name, value, context) name = value, +enum { + SSH1_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, y) + SSH2_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, + DEF_ENUM_CONTEXTUAL, DEF_ENUM_CONTEXTUAL, y) + /* Virtual packet type, for packets too short to even have a type */ + SSH_MSG_NO_TYPE_CODE = 256 +}; +#undef DEF_ENUM_UNIVERSAL +#undef DEF_ENUM_CONTEXTUAL /* * SSH-1 agent messages. @@ -987,6 +1490,7 @@ void platform_ssh_share_cleanup(const char *name); #define SSH2_AGENTC_ADD_IDENTITY 17 #define SSH2_AGENTC_REMOVE_IDENTITY 18 #define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 /* * Assorted other SSH-related enumerations. @@ -1014,8 +1518,106 @@ void platform_ssh_share_cleanup(const char *name); #define SSH2_EXTENDED_DATA_STDERR 1 /* 0x1 */ +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" + #undef TTYMODE_CHAR + #undef TTYMODE_FLAG + + /* Modes encoded differently between SSH-1 and SSH-2, for which we + * make up our own dummy opcodes to avoid confusion. */ + TTYMODE_dummy = 255, + TTYMODE_ISPEED, TTYMODE_OSPEED, + + /* Limiting value that we can use as an array bound below */ + TTYMODE_LIMIT, + + /* The real opcodes for terminal speeds. */ + TTYMODE_ISPEED_SSH1 = 192, + TTYMODE_OSPEED_SSH1 = 193, + TTYMODE_ISPEED_SSH2 = 128, + TTYMODE_OSPEED_SSH2 = 129, + + /* And the opcode that ends a list. */ + TTYMODE_END_OF_LIST = 0 +}; + +struct ssh_ttymodes { + /* A boolean per mode, indicating whether it's set. */ + bool have_mode[TTYMODE_LIMIT]; + + /* The actual value for each mode. */ + unsigned mode_val[TTYMODE_LIMIT]; +}; + +struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf); +struct ssh_ttymodes read_ttymodes_from_packet( + BinarySource *bs, int ssh_version); +void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, + struct ssh_ttymodes modes); + +const char *ssh1_pkt_type(int type); +const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type); +bool ssh2_pkt_type_code_valid(unsigned type); + /* * Need this to warn about support for the original SSH-2 keyfile * format. */ void old_keyfile_warning(void); + +/* + * Flags indicating implementation bugs that we know how to mitigate + * if we think the other end has them. + */ +#define SSH_IMPL_BUG_LIST(X) \ + X(BUG_CHOKES_ON_SSH1_IGNORE) \ + X(BUG_SSH2_HMAC) \ + X(BUG_NEEDS_SSH1_PLAIN_PASSWORD) \ + X(BUG_CHOKES_ON_RSA) \ + X(BUG_SSH2_RSA_PADDING) \ + X(BUG_SSH2_DERIVEKEY) \ + X(BUG_SSH2_REKEY) \ + X(BUG_SSH2_PK_SESSIONID) \ + X(BUG_SSH2_MAXPKT) \ + X(BUG_CHOKES_ON_SSH2_IGNORE) \ + X(BUG_CHOKES_ON_WINADJ) \ + X(BUG_SENDS_LATE_REQUEST_REPLY) \ + X(BUG_SSH2_OLDGEX) \ + /* end of list */ +#define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing, +enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) }; +#undef TMP_DECLARE_LOG2_ENUM +#define TMP_DECLARE_REAL_ENUM(thing) thing = 1 << log2_##thing, +enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_REAL_ENUM) }; +#undef TMP_DECLARE_REAL_ENUM + +/* Shared system for allocating local SSH channel ids. Expects to be + * passed a tree full of structs that have a field called 'localid' of + * type unsigned, and will check that! */ +unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); +#define alloc_channel_id(tree, type) \ + TYPECHECK(&((type *)0)->localid == (unsigned *)0, \ + alloc_channel_id_general(tree, offsetof(type, localid))) + +bool first_in_commasep_string(char const *needle, char const *haystack, + int haylen); +bool in_commasep_string(char const *needle, char const *haystack, int haylen); +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, const char *fingerprint, ssh_key *key); + +typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; +ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); +void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc); +void ssh_transient_hostkey_cache_add( + ssh_transient_hostkey_cache *thc, ssh_key *key); +bool ssh_transient_hostkey_cache_verify( + ssh_transient_hostkey_cache *thc, ssh_key *key); +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); diff --git a/ssh1bpp.c b/ssh1bpp.c new file mode 100644 index 00000000..2938e405 --- /dev/null +++ b/ssh1bpp.c @@ -0,0 +1,368 @@ +/* + * 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; + unsigned long realcrc, gotcrc; + int chunk; + PktIn *pktin; + + ssh1_cipher *cipher; + + struct crcda_ctx *crcda_ctx; + + 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 struct BinaryPacketProtocolVtable ssh1_bpp_vtable = { + ssh1_bpp_free, + ssh1_bpp_handle_input, + ssh1_bpp_handle_output, + ssh1_bpp_new_pktout, + ssh1_bpp_queue_disconnect, +}; + +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) + ssh1_cipher_free(s->cipher); + 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 struct ssh1_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); + + if (cipher) { + s->cipher = ssh1_cipher_new(cipher); + ssh1_cipher_sesskey(s->cipher, session_key); + + assert(!s->crcda_ctx); + s->crcda_ctx = crcda_make_context(); + + bpp_logevent(("Initialised %s encryption", cipher->text_name)); + } +} + +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 \ + { \ + crMaybeWaitUntilV(s->bpp.input_eof || \ + bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)); \ + if (s->bpp.input_eof) \ + goto eof; \ + } 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 < 0 || s->len > 262144) { /* SSH1.5-mandated max size */ + ssh_sw_abort(s->bpp.ssh, + "Extremely large 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 && detect_attack(s->crcda_ctx, + s->data, s->biglen, NULL)) { + ssh_sw_abort(s->bpp.ssh, + "Network attack (CRC compensation) detected!"); + crStopV; + } + + if (s->cipher) + ssh1_cipher_decrypt(s->cipher, s->data, s->biglen); + + s->realcrc = crc32_compute(s->data, s->biglen - 4); + s->gotcrc = GET_32BIT(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); + } + + 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, i, pktoffs; + unsigned long 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) */ + + for (i = pktoffs; i < 4+8; i++) + pkt->data[i] = random_byte(); + crc = crc32_compute(pkt->data + pktoffs + 4, + biglen - 4); /* all ex len */ + PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc); + PUT_32BIT(pkt->data + pktoffs, len); + + if (s->cipher) + ssh1_cipher_encrypt(s->cipher, 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 new file mode 100644 index 00000000..780dc046 --- /dev/null +++ b/ssh1censor.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(src, pkt.ptr, pkt.len); + + 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 new file mode 100644 index 00000000..a2c5e423 --- /dev/null +++ b/ssh1connection-client.c @@ -0,0 +1,530 @@ +/* + * 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 (!s->agent_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); + } else { + c = snew(struct ssh1_channel); + c->connlayer = s; + ssh1_channel_init(c); + c->remoteid = remid; + c->chan = agentf_new(&c->sc); + 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); + } + + 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 int ssh1mainchan_write( + SshChannel *sc, bool is_stderr, const void *data, int 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 struct SshChannelVtable ssh1mainchan_vtable = { + ssh1mainchan_write, + ssh1mainchan_write_eof, + NULL /* unclean_close */, + NULL /* unthrottle */, + NULL /* get_conf */, + NULL /* window_override_removed is only used by SSH-2 sharing */, + NULL /* x11_sharing_handover, likewise */, + NULL /* send_exit_status */, + NULL /* send_exit_signal */, + NULL /* send_exit_signal_numeric */, + ssh1mainchan_request_x11_forwarding, + ssh1mainchan_request_agent_forwarding, + ssh1mainchan_request_pty, + ssh1mainchan_send_env_var, + ssh1mainchan_start_shell, + ssh1mainchan_start_command, + ssh1mainchan_start_subsystem, + ssh1mainchan_send_serial_break, + ssh1mainchan_send_signal, + ssh1mainchan_send_terminal_size_change, + ssh1mainchan_hint_channel_is_simple, +}; + +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) +{ + assert(false && "Should never be called in the client"); + return NULL; +} + +SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + assert(false && "Should never be called in the client"); + return NULL; +} diff --git a/ssh1connection-server.c b/ssh1connection-server.c new file mode 100644 index 00000000..bdbec47e --- /dev/null +++ b/ssh1connection-server.c @@ -0,0 +1,357 @@ +/* + * 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 int ssh1sesschan_write(SshChannel *c, bool is_stderr, + const void *, int); +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 struct SshChannelVtable ssh1sesschan_vtable = { + ssh1sesschan_write, + ssh1sesschan_write_eof, + ssh1sesschan_initiate_close, + NULL /* unthrottle */, + NULL /* get_conf */, + NULL /* window_override_removed is only used by SSH-2 sharing */, + NULL /* x11_sharing_handover, likewise */, + ssh1sesschan_send_exit_status, + ssh1sesschan_send_exit_signal, + NULL /* send_exit_signal_numeric */, + NULL /* request_x11_forwarding */, + NULL /* request_agent_forwarding */, + NULL /* request_pty */, + NULL /* send_env_var */, + NULL /* start_shell */, + NULL /* start_command */, + NULL /* start_subsystem */, + NULL /* send_serial_break */, + NULL /* send_signal */, + NULL /* send_terminal_size_change */, + NULL /* hint_channel_is_simple */, +}; + +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); + } +} + +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) { + 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; + + 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) +{ + assert(false && "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) +{ + assert(false && "Should never be called in the server"); + return NULL; +} + +static int ssh1sesschan_write(SshChannel *sc, bool is_stderr, + const void *data, int 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); +} + +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; +} diff --git a/ssh1connection.c b/ssh1connection.c new file mode 100644 index 00000000..4d43299c --- /dev/null +++ b/ssh1connection.c @@ -0,0 +1,803 @@ +/* + * 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 struct PacketProtocolLayerVtable ssh1_connection_vtable = { + ssh1_connection_free, + ssh1_connection_process_queue, + ssh1_common_get_specials, + ssh1_connection_special_cmd, + ssh1_connection_want_user_input, + ssh1_connection_got_user_input, + ssh1_connection_reconfigure, + 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, int bufsize); +static int 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_enable_agent_fwd(ConnectionLayer *cl); +static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted); + +static const struct ConnectionLayerVtable ssh1_connlayer_vtable = { + ssh1_rportfwd_alloc, + ssh1_rportfwd_remove, + ssh1_lportfwd_open, + ssh1_session_open, + ssh1_serverside_x11_open, + ssh1_serverside_agent_open, + ssh1_add_x11_display, + NULL /* add_sharing_x11_display */, + NULL /* remove_sharing_x11_display */, + NULL /* send_packet_from_downstream */, + NULL /* alloc_sharing_channel */, + NULL /* delete_sharing_channel */, + NULL /* sharing_queue_global_request */, + NULL /* sharing_no_more_downstreams */, + ssh1_agent_forwarding_permitted, + ssh1_terminal_size, + ssh1_stdout_unthrottle, + ssh1_stdin_backlog, + ssh1_throttle_all_channels, + ssh1_ldisc_option, + ssh1_set_ldisc_option, + ssh1_enable_x_fwd, + ssh1_enable_agent_fwd, + ssh1_set_wants_user_input, +}; + +static int ssh1channel_write( + SshChannel *c, bool is_stderr, const void *buf, int len); +static void ssh1channel_write_eof(SshChannel *c); +static void ssh1channel_initiate_close(SshChannel *c, const char *err); +static void ssh1channel_unthrottle(SshChannel *c, int bufsize); +static Conf *ssh1channel_get_conf(SshChannel *c); +static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ } + +static const struct SshChannelVtable ssh1channel_vtable = { + ssh1channel_write, + ssh1channel_write_eof, + ssh1channel_initiate_close, + ssh1channel_unthrottle, + ssh1channel_get_conf, + ssh1channel_window_override_removed, + NULL /* x11_sharing_handover is only used by SSH-2 connection sharing */, + NULL /* send_exit_status */, + NULL /* send_exit_signal */, + NULL /* send_exit_signal_numeric */, + NULL /* request_x11_forwarding */, + NULL /* request_agent_forwarding */, + NULL /* request_pty */, + NULL /* send_env_var */, + NULL /* start_shell */, + NULL /* start_command */, + NULL /* start_subsystem */, + NULL /* send_serial_break */, + NULL /* send_signal */, + NULL /* send_terminal_size_change */, + NULL /* hint_channel_is_simple */, +}; + +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->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); + + 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); + + 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 */ + const char *msg = chan_log_close_msg(c->chan); + + if (msg != NULL) + ppl_logevent(("%s%s%s", msg, reason ? " " : "", reason ? reason : "")); + + 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, int 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 int ssh1channel_write( + SshChannel *sc, bool is_stderr, const void *buf, int 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, int 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 int 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_enable_agent_fwd(ConnectionLayer *cl) +{ + struct ssh1_connection_state *s = + container_of(cl, struct ssh1_connection_state, cl); + + s->agent_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. + */ + void *data; + int len; + bufchain_prefix(s->ppl.user_input, &data, &len); + if (len > 512) + len = 512; + sshfwd_write(&s->mainchan_sc, data, len); + bufchain_consume(s->ppl.user_input, 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 new file mode 100644 index 00000000..fe4c6157 --- /dev/null +++ b/ssh1connection.h @@ -0,0 +1,119 @@ +struct ssh1_channel; + +struct outstanding_succfail; + +struct ssh1_connection_state { + int crState; + + Ssh *ssh; + + 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; + + bool agent_fwd_enabled; + + 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 */ + + 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); diff --git a/ssh1login-server.c b/ssh1login-server.c new file mode 100644 index 00000000..6689bf64 --- /dev/null +++ b/ssh1login-server.c @@ -0,0 +1,420 @@ +/* + * Packet protocol layer for the SSH-1 login phase, from the server side. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" + +struct ssh1_login_server_state { + int crState; + + PacketProtocolLayer *successor_layer; + + 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; + + struct RSAKey *servkey, *hostkey; + bool servkey_generated_here; + Bignum sesskey; + + AuthPolicy *authpolicy; + unsigned ap_methods, current_method; + unsigned char auth_rsa_expected_response[16]; + struct 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 struct PacketProtocolLayerVtable ssh1_login_server_vtable = { + ssh1_login_server_free, + ssh1_login_server_process_queue, + ssh1_login_server_get_specials, + ssh1_login_server_special_cmd, + ssh1_login_server_want_user_input, + ssh1_login_server_got_user_input, + ssh1_login_server_reconfigure, + NULL /* no layer names in SSH-1 */, +}; + +static void no_progress(void *param, int action, int phase, int iprogress) {} + +PacketProtocolLayer *ssh1_login_server_new( + PacketProtocolLayer *successor_layer, struct RSAKey *hostkey, + AuthPolicy *authpolicy) +{ + 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->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(struct RSAKey); + rsa_generate(s->servkey, server_key_bits, no_progress, NULL); + s->servkey->comment = NULL; + s->servkey_generated_here = true; + } + + s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED; + /* FIXME: ability to configure this to a subset */ + s->supported_ciphers_mask = ((1U << SSH_CIPHER_3DES) | + (1U << SSH_CIPHER_BLOWFISH) | + (1U << SSH_CIPHER_DES)); + 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); + + for (i = 0; i < 8; i++) + s->cookie[i] = random_byte(); + + 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; + } + + { + struct RSAKey *smaller, *larger; + strbuf *data = strbuf_new(); + + if (bignum_bitcount(s->hostkey->modulus) > + bignum_bitcount(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)) { + freebn(s->sesskey); + s->sesskey = bignum_from_bytes(data->u, data->len); + data->len = 0; + 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)); + freebn(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 struct ssh1_cipheralg *cipher = + (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish : + s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des); + 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; + + { + Bignum modulus = get_mp_ssh1(pktin); + s->authkey = auth_publickey_ssh1( + s->authpolicy, s->username, modulus); + freebn(modulus); + } + + if (!s->authkey) + continue; + + if (s->authkey->bytes < 32) { + ppl_logevent(("Auth key far too small")); + continue; + } + + { + unsigned char *rsabuf = + snewn(s->authkey->bytes, unsigned char); + struct MD5Context md5c; + + for (i = 0; i < 32; i++) + rsabuf[i] = random_byte(); + + MD5Init(&md5c); + put_data(&md5c, rsabuf, 32); + put_data(&md5c, s->session_id, 16); + MD5Final(s->auth_rsa_expected_response, &md5c); + + if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) { + sfree(rsabuf); + ppl_logevent(("Failed to encrypt auth challenge")); + continue; + } + + Bignum bn = bignum_from_bytes(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); + + freebn(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; + } + + { + 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 new file mode 100644 index 00000000..a85209a3 --- /dev/null +++ b/ssh1login.c @@ -0,0 +1,1168 @@ +/* + * 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 "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" + +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 */ + int keyi, nkeys; + bool authed; + struct RSAKey key; + Bignum challenge; + ptrlen comment; + int dlgret; + Filename *keyfile; + struct RSAKey servkey, hostkey; + bool want_user_input; + + 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 struct PacketProtocolLayerVtable ssh1_login_vtable = { + ssh1_login_free, + ssh1_login_process_queue, + ssh1_common_get_specials, + ssh1_login_special_cmd, + ssh1_login_want_user_input, + ssh1_login_got_user_input, + ssh1_login_reconfigure, + 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); + 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_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); + + for (i = 0; i < 32; i++) + s->session_key[i] = random_byte(); + + /* + * 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 = (s->hostkey.bytes > s->servkey.bytes ? + s->hostkey.bytes : s->servkey.bytes); + + s->rsabuf = snewn(s->len, unsigned char); + + /* + * Verify the host key. + */ + { + /* + * First format the key into a string. + */ + int len = rsastr_len(&s->hostkey); + char *fingerprint; + char *keystr = snewn(len, char); + rsastr_fmt(keystr, &s->hostkey); + fingerprint = rsa_ssh1_fingerprint(&s->hostkey); + + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL); + sfree(fingerprint); + if (s->dlgret == 0) { /* did not match */ + 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 */ + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, + "rsa", keystr, fingerprint, ssh1_login_dialog_callback, s); + 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 { + 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]; + } + + { + struct RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? + &s->servkey : &s->hostkey); + struct 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 = SSH_CIPHER_3DES; + cipher_string = "3DES"; break; + case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH; + cipher_string = "Blowfish"; break; + case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES; + cipher_string = "single-DES"; break; + } + if (s->supported_ciphers_mask & (1 << s->cipher_type)) + cipher_chosen = true; + } + } + if (!cipher_chosen) { + if ((s->supported_ciphers_mask & (1 << SSH_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 SSH_CIPHER_3DES: + ppl_logevent(("Using 3DES encryption")); + break; + case SSH_CIPHER_DES: + ppl_logevent(("Using single-DES encryption")); + break; + case SSH_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 struct ssh1_cipheralg *cipher = + (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish : + s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des); + ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); + } + + if (s->servkey.modulus) { + sfree(s->servkey.modulus); + s->servkey.modulus = NULL; + } + if (s->servkey.exponent) { + sfree(s->servkey.exponent); + s->servkey.exponent = NULL; + } + if (s->hostkey.modulus) { + sfree(s->hostkey.modulus); + s->hostkey.modulus = NULL; + } + if (s->hostkey.exponent) { + sfree(s->hostkey.exponent); + s->hostkey.exponent = NULL; + } + 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->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 = dupstr(s->cur_prompt->prompts[0]->result); + 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 ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) + 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 \"%.150s\"", + 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 (rsa_ssh1_loadpub(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 = rsa_ssh1_encrypted(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( + s->asrc, s->agent_response.ptr, s->agent_response.len); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + s->nkeys = toint(get_uint32(s->asrc)); + if (s->nkeys < 0) { + ppl_logevent(("Pageant reported negative key count %d", + s->nkeys)); + s->nkeys = 0; + } + ppl_logevent(("Pageant has %d SSH-1 keys", s->nkeys)); + for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { + size_t start, end; + start = s->asrc->pos; + get_rsa_ssh1_pub(s->asrc, &s->key, + RSA_SSH1_EXPONENT_FIRST); + end = s->asrc->pos; + s->comment = get_string(s->asrc); + if (get_err(s->asrc)) { + ppl_logevent(("Pageant key list packet was truncated")); + break; + } + if (s->publickey_blob) { + ptrlen keystr = make_ptrlen( + (const char *)s->asrc->data + start, end - start); + + if (keystr.len == s->publickey_blob->len && + !memcmp(keystr.ptr, s->publickey_blob->s, + s->publickey_blob->len)) { + ppl_logevent(("Pageant key #%d matches " + "configured key file", s->keyi)); + s->tried_publickey = true; + } else + /* Skip non-configured key */ + continue; + } + ppl_logevent(("Trying Pageant key #%d", s->keyi)); + 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_AUTH_RSA_CHALLENGE) { + ppl_logevent(("Key refused")); + continue; + } + ppl_logevent(("Received RSA challenge")); + s->challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + freebn(s->challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + + { + strbuf *agentreq; + const char *ret; + + agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); + put_uint32(agentreq, bignum_bitcount(s->key.modulus)); + put_mp_ssh1(agentreq, s->key.exponent); + put_mp_ssh1(agentreq, s->key.modulus); + put_mp_ssh1(agentreq, s->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); + + 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); + sfree((char *)ret); + crMaybeWaitUntilV( + (pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent(("Pageant's response " + "accepted")); + if (flags & FLAG_VERBOSE) { + ppl_printf(("Authenticated using RSA " + "key \"%.*s\" from " + "agent\r\n", PTRLEN_PRINTF( + s->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")); + } + } + freebn(s->key.exponent); + freebn(s->key.modulus); + freebn(s->challenge); + if (s->authed) + break; + } + sfree(s->agent_response_to_free); + s->agent_response_to_free = NULL; + if (s->publickey_blob && !s->tried_publickey) + ppl_logevent(("Configured key file not in Pageant")); + } else { + ppl_logevent(("Failed to get reply from Pageant")); + } + 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 (flags & FLAG_VERBOSE) + 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 (flags & FLAG_VERBOSE) + ppl_printf(("No passphrase required.\r\n")); + passphrase = NULL; + } else { + s->cur_prompt = new_prompts(s->ppl.seat); + s->cur_prompt->to_server = false; + s->cur_prompt->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + s->publickey_comment), false); + 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 = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + s->cur_prompt = NULL; + } + /* + * Try decrypting key with passphrase. + */ + retd = rsa_ssh1_loadkey( + 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 { + assert(0 && "unexpected return from rsa_ssh1_loadkey()"); + got_passphrase = false; /* placate optimisers */ + } + } + + 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]; + Bignum challenge, response; + + challenge = get_mp_ssh1(pktin); + if (get_err(pktin)) { + freebn(challenge); + ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " + "was badly formatted"); + return; + } + response = rsa_ssh1_decrypt(challenge, &s->key); + freebn(s->key.private_exponent);/* burn the evidence */ + + for (i = 0; i < 32; i++) { + buffer[i] = bignum_byte(response, 31 - i); + } + + { + struct MD5Context md5c; + MD5Init(&md5c); + put_data(&md5c, buffer, 32); + put_data(&md5c, s->session_id, 16); + MD5Final(buffer, &md5c); + } + + 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); + + freebn(challenge); + freebn(response); + } + + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_FAILURE) { + if (flags & FLAG_VERBOSE) + 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(s->ppl.seat); + + if (conf_get_bool(s->conf, CONF_try_tis_auth) && + (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && + !s->tis_auth_refused) { + s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; + 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 (flags & FLAG_INTERACTIVE) + ppl_printf(("TIS authentication refused.\r\n")); + s->tis_auth_refused = true; + continue; + } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { + ptrlen challenge; + char *instr_suf, *prompt; + + 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->name = dupstr("SSH TIS authentication"); + /* Prompt heuristic comes from OpenSSH */ + if (!memchr(challenge.ptr, '\n', challenge.len)) { + instr_suf = dupstr(""); + prompt = mkstr(challenge); + } else { + instr_suf = mkstr(challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using TIS authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = true; + add_prompt(s->cur_prompt, prompt, false); + sfree(instr_suf); + } 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) { + 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; + char *instr_suf, *prompt; + + 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->name = dupstr("SSH CryptoCard authentication"); + s->cur_prompt->name_reqd = false; + /* Prompt heuristic comes from OpenSSH */ + if (!memchr(challenge.ptr, '\n', challenge.len)) { + instr_suf = dupstr(""); + prompt = mkstr(challenge); + } else { + instr_suf = mkstr(challenge); + prompt = dupstr("Response: "); + } + s->cur_prompt->instruction = + dupprintf("Using CryptoCard authentication.%s%s", + (*instr_suf) ? "\n" : "", + instr_suf); + s->cur_prompt->instr_reqd = true; + add_prompt(s->cur_prompt, prompt, false); + sfree(instr_suf); + } 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->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; + + pwlen = strlen(s->cur_prompt->prompts[0]->result); + if (pwlen < 16) { + bottom = 0; /* zero length passwords are OK! :-) */ + top = 15; + } else { + bottom = pwlen & ~7; + top = bottom + 7; + } + + assert(pwlen >= bottom && pwlen <= top); + + for (i = bottom; i <= top; i++) { + if (i == pwlen) { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, s->cur_prompt->prompts[0]->result); + pq_push(s->ppl.out_pq, pkt); + } else { + int j; + strbuf *random_data = strbuf_new(); + for (j = 0; j < i; j++) + put_byte(random_data, random_byte()); + + 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(); + + ppl_logevent(("Sending length-padded password")); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_asciz(padded_pw, s->cur_prompt->prompts[0]->result); + do { + put_byte(padded_pw, random_byte()); + } while (padded_pw->len % 64 != 0); + 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, s->cur_prompt->prompts[0]->result); + pq_push(s->ppl.out_pq, pkt); + } + } else { + pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); + put_stringz(pkt, s->cur_prompt->prompts[0]->result); + 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 (flags & FLAG_VERBOSE) + 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_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 new file mode 100644 index 00000000..cabb2cd1 --- /dev/null +++ b/ssh2bpp-bare.c @@ -0,0 +1,186 @@ +/* + * 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 struct BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = { + ssh2_bare_bpp_free, + ssh2_bare_bpp_handle_input, + ssh2_bare_bpp_handle_output, + ssh2_bare_bpp_new_pktout, + ssh2_bpp_queue_disconnect, /* in sshcommon.c */ +}; + +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 \ + { \ + crMaybeWaitUntilV(s->bpp.input_eof || \ + bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)); \ + if (s->bpp.input_eof) \ + goto eof; \ + } 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); + + /* + * 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; + } + + 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(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 new file mode 100644 index 00000000..4d117878 --- /dev/null +++ b/ssh2bpp.c @@ -0,0 +1,903 @@ +/* + * 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; + ssh2_cipher *cipher; + ssh2_mac *mac; + bool etm_mode; + const struct 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; + + 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 struct BinaryPacketProtocolVtable ssh2_bpp_vtable = { + ssh2_bpp_free, + ssh2_bpp_handle_input, + ssh2_bpp_handle_output, + ssh2_bpp_new_pktout, + ssh2_bpp_queue_disconnect, /* in sshcommon.c */ +}; + +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(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + sfree(s->buf); + if (s->out.cipher) + ssh2_cipher_free(s->out.cipher); + if (s->out.mac) + ssh2_mac_free(s->out.mac); + if (s->out_comp) + ssh_compressor_free(s->out_comp); + if (s->in.cipher) + ssh2_cipher_free(s->in.cipher); + if (s->in.mac) + ssh2_mac_free(s->in.mac); + if (s->in_decomp) + ssh_decompressor_free(s->in_decomp); + sfree(s->pktin); + sfree(s); +} + +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipheralg *cipher, const void *ckey, const void *iv, + const struct ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const struct 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); + + if (s->out.cipher) + ssh2_cipher_free(s->out.cipher); + if (s->out.mac) + ssh2_mac_free(s->out.mac); + if (s->out_comp) + ssh_compressor_free(s->out_comp); + + if (cipher) { + s->out.cipher = ssh2_cipher_new(cipher); + ssh2_cipher_setkey(s->out.cipher, ckey); + ssh2_cipher_setiv(s->out.cipher, iv); + + s->cbc_ignore_workaround = ( + (ssh2_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) && + !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); + + bpp_logevent(("Initialised %.200s outbound encryption", + ssh2_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); + mac->setkey(s->out.mac, mac_key); + + bpp_logevent(("Initialised %.200s outbound MAC algorithm%s%s", + ssh2_mac_alg(s->out.mac)->text_name, + etm_mode ? " (in ETM mode)" : "", + (s->out.cipher && + ssh2_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 struct ssh2_cipheralg *cipher, const void *ckey, const void *iv, + const struct ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const struct 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); + + if (s->in.cipher) + ssh2_cipher_free(s->in.cipher); + if (s->in.mac) + ssh2_mac_free(s->in.mac); + if (s->in_decomp) + ssh_decompressor_free(s->in_decomp); + + if (cipher) { + s->in.cipher = ssh2_cipher_new(cipher); + ssh2_cipher_setkey(s->in.cipher, ckey); + ssh2_cipher_setiv(s->in.cipher, iv); + + bpp_logevent(("Initialised %.200s inbound encryption", + ssh2_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); + mac->setkey(s->in.mac, mac_key); + + bpp_logevent(("Initialised %.200s inbound MAC algorithm%s%s", + ssh2_mac_alg(s->in.mac)->text_name, + etm_mode ? " (in ETM mode)" : "", + (s->in.cipher && + ssh2_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 \ + { \ + crMaybeWaitUntilV(s->bpp.input_eof || \ + bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)); \ + if (s->bpp.input_eof) \ + goto eof; \ + } 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 = ssh2_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 && + (ssh2_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). */ + ssh2_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(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 && (ssh2_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); + ssh2_cipher_decrypt_length( + s->in.cipher, len, 4, s->in.sequence); + s->len = toint(GET_32BIT(len)); + } else { + s->len = toint(GET_32BIT(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) + ssh2_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) + ssh2_cipher_decrypt( + s->in.cipher, s->buf, s->cipherblk); + + /* + * Now get the length figure. + */ + s->len = toint(GET_32BIT(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) + ssh2_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; + } + + pq_push(&s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + s->pktin = NULL; + + if (type == SSH2_MSG_NEWKEYS) { + /* + * 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 (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: + 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 ? ssh2_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, random_byte()); + pkt->data[4] = padding; + PUT_32BIT(pkt->data, origlen + padding - 4); + + /* Encrypt length if the scheme requires it */ + if (s->out.cipher && + (ssh2_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + ssh2_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) + ssh2_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) + ssh2_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 ? ssh2_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); + while (length-- > 0) + put_byte(ignore_pkt, random_byte()); + 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) < + (ssh2_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 new file mode 100644 index 00000000..68d9d61c --- /dev/null +++ b/ssh2censor.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(src, pkt.ptr, pkt.len); + + 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 new file mode 100644 index 00000000..d332c677 --- /dev/null +++ b/ssh2connection-client.c @@ -0,0 +1,478 @@ +/* + * 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 (!s->agent_fwd_enabled) { + CHANOPEN_RETURN_FAILURE( + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + ("Agent forwarding is not enabled")); + } + + 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) +{ + assert(false && "Should never be called in the client"); + return 0; /* placate optimiser */ +} + +SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) +{ + assert(false && "Should never be called in the client"); + return 0; /* placate optimiser */ +} + +static void ssh2_channel_response( + struct ssh2_channel *c, PktIn *pkt, void *ctx) +{ + chan_request_response(c->chan, 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) +{ + assert(false && "Should never be called in the client"); +} + +void ssh2channel_send_exit_signal( + SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) +{ + assert(false && "Should never be called in the client"); +} + +void ssh2channel_send_exit_signal_numeric( + SshChannel *sc, int signum, bool core_dumped, ptrlen msg) +{ + assert(false && "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); +} diff --git a/ssh2connection-server.c b/ssh2connection-server.c new file mode 100644 index 00000000..7cd7441a --- /dev/null +++ b/ssh2connection-server.c @@ -0,0 +1,299 @@ +/* + * 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) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + s->sftpserver_vt = sftpserver_vt; +} + +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)); +} + +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) +{ + assert(false && "Should never be called in the server"); +} + +void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) +{ + assert(false && "Should never be called in the server"); +} + +SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) +{ + assert(false && "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) +{ + assert(false && "Should never be called in the server"); +} + +void ssh2channel_start_command( + SshChannel *sc, bool want_reply, const char *command) +{ + assert(false && "Should never be called in the server"); +} + +bool ssh2channel_start_subsystem( + SshChannel *sc, bool want_reply, const char *subsystem) +{ + assert(false && "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) +{ + assert(false && "Should never be called in the server"); +} + +void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) +{ + assert(false && "Should never be called in the server"); +} + +void ssh2channel_request_pty( + SshChannel *sc, bool want_reply, Conf *conf, int w, int h) +{ + assert(false && "Should never be called in the server"); +} + +bool ssh2channel_send_env_var( + SshChannel *sc, bool want_reply, const char *var, const char *value) +{ + assert(false && "Should never be called in the server"); +} + +bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) +{ + assert(false && "Should never be called in the server"); +} + +bool ssh2channel_send_signal( + SshChannel *sc, bool want_reply, const char *signame) +{ + assert(false && "Should never be called in the server"); +} + +void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) +{ + assert(false && "Should never be called in the server"); +} diff --git a/ssh2connection.c b/ssh2connection.c new file mode 100644 index 00000000..f1135309 --- /dev/null +++ b/ssh2connection.c @@ -0,0 +1,1691 @@ +/* + * 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 struct PacketProtocolLayerVtable ssh2_connection_vtable = { + ssh2_connection_free, + ssh2_connection_process_queue, + ssh2_connection_get_specials, + ssh2_connection_special_cmd, + ssh2_connection_want_user_input, + ssh2_connection_got_user_input, + ssh2_connection_reconfigure, + "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, int bufsize); +static int 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_enable_agent_fwd(ConnectionLayer *cl); +static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted); + +static const struct ConnectionLayerVtable ssh2_connlayer_vtable = { + ssh2_rportfwd_alloc, + ssh2_rportfwd_remove, + ssh2_lportfwd_open, + ssh2_session_open, + ssh2_serverside_x11_open, + ssh2_serverside_agent_open, + ssh2_add_x11_display, + ssh2_add_sharing_x11_display, + ssh2_remove_sharing_x11_display, + ssh2_send_packet_from_downstream, + ssh2_alloc_sharing_channel, + ssh2_delete_sharing_channel, + ssh2_sharing_queue_global_request, + ssh2_sharing_no_more_downstreams, + ssh2_agent_forwarding_permitted, + ssh2_terminal_size, + ssh2_stdout_unthrottle, + ssh2_stdin_backlog, + ssh2_throttle_all_channels, + ssh2_ldisc_option, + ssh2_set_ldisc_option, + ssh2_enable_x_fwd, + ssh2_enable_agent_fwd, + 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 int ssh2channel_write( + SshChannel *c, bool is_stderr, const void *buf, int len); +static void ssh2channel_write_eof(SshChannel *c); +static void ssh2channel_initiate_close(SshChannel *c, const char *err); +static void ssh2channel_unthrottle(SshChannel *c, int 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 struct SshChannelVtable ssh2channel_vtable = { + ssh2channel_write, + ssh2channel_write_eof, + ssh2channel_initiate_close, + ssh2channel_unthrottle, + ssh2channel_get_conf, + ssh2channel_window_override_removed, + ssh2channel_x11_sharing_handover, + ssh2channel_send_exit_status, + ssh2channel_send_exit_signal, + ssh2channel_send_exit_signal_numeric, + ssh2channel_request_x11_forwarding, + ssh2channel_request_agent_forwarding, + ssh2channel_request_pty, + ssh2channel_send_env_var, + ssh2channel_start_shell, + ssh2channel_start_command, + ssh2channel_start_subsystem, + ssh2channel_send_serial_break, + ssh2channel_send_signal, + ssh2channel_send_terminal_size_change, + 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 int 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); + + 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->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); + + 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); + + /* + * 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( + bs_modes, encoded_modes.ptr, encoded_modes.len); + 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)); + 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); + + /* + * 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); + + /* + * 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 int ssh2_try_send(struct ssh2_channel *c) +{ + struct ssh2_connection_state *s = c->connlayer; + PktOut *pktout; + int bufsize; + + while (c->remwindow > 0 && + (bufchain_size(&c->outbuffer) > 0 || + bufchain_size(&c->errbuffer) > 0)) { + int len; + void *data; + bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ? + &c->errbuffer : &c->outbuffer); + + bufchain_prefix(buf, &data, &len); + if ((unsigned)len > c->remwindow) + len = c->remwindow; + if ((unsigned)len > c->remmaxpkt) + 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_string(pktout, data, len); + pq_push(s->ppl.out_pq, pktout); + bufchain_consume(buf, len); + c->remwindow -= 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 (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, int bufsize) +{ + struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); + struct ssh2_connection_state *s = c->connlayer; + int 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 int ssh2channel_write( + SshChannel *sc, bool is_stderr, const void *buf, int 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, int bufsize) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + if (s->mainchan) + sshfwd_unthrottle(s->mainchan_sc, bufsize); +} + +static int 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++) + 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_enable_agent_fwd(ConnectionLayer *cl) +{ + struct ssh2_connection_state *s = + container_of(cl, struct ssh2_connection_state, cl); + + s->agent_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. + */ + void *data; + int len; + bufchain_prefix(s->ppl.user_input, &data, &len); + sshfwd_write(s->mainchan_sc, data, len); + bufchain_consume(s->ppl.user_input, 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 new file mode 100644 index 00000000..1c70aca4 --- /dev/null +++ b/ssh2connection.h @@ -0,0 +1,231 @@ +#ifndef PUTTY_SSH2CONNECTION_H +#define PUTTY_SSH2CONNECTION_H + +struct outstanding_channel_request; +struct outstanding_global_request; + +struct ssh2_connection_state { + int crState; + + Ssh *ssh; + + 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; + + Conf *conf; + + tree234 *channels; /* indexed by local id */ + bool all_channels_throttled; + + bool X11_fwd_enabled; + tree234 *x11authtree; + + bool got_pty; + bool agent_fwd_enabled; + + tree234 *rportfwds; + PortFwdManager *portfwdmgr; + bool portfwdmgr_configured; + + const SftpServerVtable *sftpserver_vt; + + /* + * 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); + +#endif /* PUTTY_SSH2CONNECTION_H */ diff --git a/ssh2kex-client.c b/ssh2kex-client.c new file mode 100644 index 00000000..bff5b284 --- /dev/null +++ b/ssh2kex-client.c @@ -0,0 +1,870 @@ +/* + * 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" + +void ssh2kex_coroutine(struct ssh2_transport_state *s) +{ + 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)); + 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"); + 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; + } 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", + s->kex_alg->hash->text_name)); + /* + * 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)); + 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"); + 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); + 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; + freebn(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + freebn(s->g); s->g = NULL; + freebn(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), + s->kex_alg->hash->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"); + 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)); + 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.ptr, keydata.len); + if (!get_err(pktin) && !s->K) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in ECDH reply"); + return; + } + } + + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet"); + 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->fingerprint = 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)); + 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"); + 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", s->kex_alg->hash->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"); + 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); + 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"); + 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; + /* Save expiration time of cred when delegating */ + if (s->gss_delegate && s->gss_cred_expiry != GSS_NO_EXPIRATION) + s->gss_cred_expiry = s->gss_cred_expiry; + /* 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_string(s->exhash, + s->hostkeydata.ptr, s->hostkeydata.len); + } + /* + * 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)); + return; + } + } while (s->gss_rcvtok.length || + s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || + !s->complete_rcvd); + + 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; + freebn(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + freebn(s->g); s->g = NULL; + freebn(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", + s->kex_alg->hash->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)); + 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.ptr, rsakeydata.len); + if (!s->rsa_kex_key) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse RSA public key packet"); + return; + } + + 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); + int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49); + int i, byte = 0; + strbuf *buf; + unsigned char *outstr; + int outstrlen; + + s->K = bn_power_2(nbits - 1); + + for (i = 0; i < nbits; i++) { + if ((i & 7) == 0) { + byte = random_byte(); + } + bignum_set_bit(s->K, i, (byte >> (i & 7)) & 1); + } + + /* + * Encode this as an mpint. + */ + buf = strbuf_new(); + put_mp_ssh2(buf, s->K); + + /* + * Encrypt it with the given RSA key. + */ + outstrlen = (klen + 7) / 8; + outstr = snewn(outstrlen, unsigned char); + ssh_rsakex_encrypt(s->kex_alg->hash, buf->u, buf->len, + outstr, outstrlen, s->rsa_kex_key); + + /* + * And send it off in a return packet. + */ + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET); + put_string(pktout, outstr, outstrlen); + pq_push(s->ppl.out_pq, pktout); + + put_string(s->exhash, outstr, outstrlen); + + strbuf_free(buf); + sfree(outstr); + } + + ssh_rsakex_freekey(s->rsa_kex_key); + s->rsa_kex_key = NULL; + + 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)); + return; + } + + s->sigdata = get_string(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature"); + 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"); + } + 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"); + 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"); + 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) { + s->fingerprint = ssh2_fingerprint(s->hkey); + ppl_logevent(("GSS kex provided fallback host key:")); + ppl_logevent(("%s", s->fingerprint)); + sfree(s->fingerprint); + s->fingerprint = NULL; + 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 */ + s->fingerprint = ssh2_fingerprint(s->hkey); + + if (s->need_gss_transient_hostkey) { + ppl_logevent(("Post-GSS rekey provided fallback host key:")); + ppl_logevent(("%s", 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", s->fingerprint)); + ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any " + "used in previous GSS kex"); + return; + } + + sfree(s->fingerprint); + s->fingerprint = NULL; + } + } 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.) + */ + s->fingerprint = ssh2_fingerprint(s->hkey); + ppl_logevent(("Host key fingerprint is:")); + ppl_logevent(("%s", s->fingerprint)); + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key( + s->conf, s->fingerprint, s->hkey); + if (s->dlgret == 0) { /* did not match */ + ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), s->keystr, s->fingerprint, + ssh2_transport_dialog_callback, s); +#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; + } + } + sfree(s->fingerprint); + s->fingerprint = NULL; + /* + * 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) { + s->fingerprint = ssh2_fingerprint(s->hkey); + ppl_logevent(("Storing additional host key for this host:")); + ppl_logevent(("%s", s->fingerprint)); + sfree(s->fingerprint); + s->fingerprint = NULL; + store_host_key(s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), s->keystr); + s->cross_certifying = false; + /* + * 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. + */ + if (strcmp(s->hostkey_str, s->keystr)) { +#ifndef FUZZING + ssh_sw_abort(s->ppl.ssh, + "Host key was different in repeat key exchange"); + 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 new file mode 100644 index 00000000..893add05 --- /dev/null +++ b/ssh2kex-server.c @@ -0,0 +1,289 @@ +/* + * 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 "storage.h" +#include "ssh2transport.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, s->exchange_hash, s->kex_alg->hash->hlen, + BinarySink_UPCAST(sb)); + return sb; +} + +static void no_progress(void *param, int action, int phase, int iprogress) +{ +} + +void ssh2kex_coroutine(struct ssh2_transport_state *s) +{ + 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); + } + + s->hostkeyblob->len = 0; + 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)); + 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. + */ + s->p = primegen(s->pbits, 2, 2, NULL, 1, no_progress, NULL, 1); + s->g = bignum_from_long(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", + s->kex_alg->hash->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)); + return; + } + s->f = get_mp_ssh2(pktin); + if (get_err(pktin)) { + ssh_proto_error(s->ppl.ssh, + "Unable to parse Diffie-Hellman initial packet"); + 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); + 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; + freebn(s->f); s->f = NULL; + if (dh_is_gex(s->kex_alg)) { + freebn(s->g); s->g = NULL; + freebn(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), + s->kex_alg->hash->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"); + 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)); + return; + } + + { + ptrlen keydata = get_string(pktin); + put_stringpl(s->exhash, keydata); + + s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata.ptr, keydata.len); + if (!get_err(pktin) && !s->K) { + ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " + "point in ECDH initial packet"); + 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", + s->kex_alg->hash->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; + + s->rsa_kex_key = snew(struct RSAKey); + rsa_generate(s->rsa_kex_key, extra->minklen, no_progress, NULL); + s->rsa_kex_key->comment = NULL; + } + + 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)); + return; + } + + { + ptrlen encrypted_secret = get_string(pktin); + put_stringpl(s->exhash, encrypted_secret); + s->K = ssh_rsakex_decrypt( + s->kex_alg->hash, encrypted_secret, s->rsa_kex_key); + } + + if (!s->K) { + ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); + return; + } + + ssh_rsakex_freekey(s->rsa_kex_key); + s->rsa_kex_key = NULL; + + 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 new file mode 100644 index 00000000..e6803c56 --- /dev/null +++ b/ssh2transhk.c @@ -0,0 +1,124 @@ +/* + * 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); +} + +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) { + 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 new file mode 100644 index 00000000..2a713413 --- /dev/null +++ b/ssh2transport.c @@ -0,0 +1,1975 @@ +/* + * 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 "storage.h" +#include "ssh2transport.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 struct ssh2_macalg *const macs[] = { + &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 +}; +const static struct 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, + unsigned char *block, int len, + unsigned char **outblock, int *outlen, + int minlen) +{ +} +static bool ssh_decomp_none_block(ssh_decompressor *handle, + unsigned char *block, int len, + unsigned char **outblock, int *outlen) +{ + return false; +} +const static struct ssh_compression_alg ssh_comp_none = { + "none", NULL, + ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block, + ssh_decomp_none_init, ssh_decomp_none_cleanup, ssh_decomp_none_block, + NULL +}; +const static struct 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 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 struct PacketProtocolLayerVtable ssh2_transport_vtable = { + ssh2_transport_free, + ssh2_transport_process_queue, + ssh2_transport_get_specials, + ssh2_transport_special_cmd, + ssh2_transport_want_user_input, + ssh2_transport_got_user_input, + ssh2_transport_reconfigure, + 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 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" +}; + +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, + bool is_server) +{ + 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 (is_server) { + s->client_kexinit = s->incoming_kexinit; + s->server_kexinit = s->outgoing_kexinit; + s->out.mkkey_adjust = 1; + } else { + s->client_kexinit = s->outgoing_kexinit; + s->server_kexinit = s->incoming_kexinit; + s->in.mkkey_adjust = 1; + } + + 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); + sfree(s->fingerprint); + if (s->hkey && !s->hostkeys) { + ssh_key_free(s->hkey); + s->hkey = NULL; + } + if (s->f) freebn(s->f); + if (s->p) freebn(s->p); + if (s->g) freebn(s->g); + if (s->K) freebn(s->K); + if (s->dh_ctx) + dh_cleanup(s->dh_ctx); + if (s->rsa_kex_key) + ssh_rsakex_freekey(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); + sfree(s); +} + +/* + * SSH-2 key derivation (RFC 4253 section 7.2). + */ +static void ssh2_mkkey( + struct ssh2_transport_state *s, strbuf *out, + Bignum 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; + + out->len = 0; + 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_final(h, key); + + /* Subsequent blocks of hlen bytes. */ + if (keylen_padded > hlen) { + int offset; + + 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); + + for (offset = hlen; offset < keylen_padded; offset += hlen) { + put_data(h, key + offset - hlen, hlen); + ssh_hash *h2 = ssh_hash_copy(h); + ssh_hash_final(h2, 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]; + } + assert(!"No space in KEXINIT list"); + return NULL; +} + +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)); + pq_pop(ppl->in_pq); + 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; + + 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) +{ + ssh2_transport_filter_queue(s); + return pq_pop(s->ppl.in_pq); +} + +static void ssh2_write_kexinit_lists( + BinarySink *pktout, + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + Conf *conf, 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 struct ssh_kexes *preferred_kex[KEX_MAX + 1]; /* +1 for GSSAPI */ + int n_preferred_hk; + int preferred_hk[HK_MAX]; + int n_preferred_ciphers; + const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; + const struct ssh_compression_alg *preferred_comp; + const struct 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 struct 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++) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + ssh_key_alg(our_hostkeys[i])->ssh_id); + alg->u.hk.hostkey = ssh_key_alg(our_hostkeys[i]); + 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 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 (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 struct 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]->name); + 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 struct 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(); + for (j = 0; j < MAXKEXLIST; j++) { + if (kexlists[i][j].name == NULL) break; + add_to_commasep(list, kexlists[i][j].name); + } + 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 struct 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]) +{ + BinarySource client[1], server[1]; + int i; + bool guess_correct; + ptrlen clists[NKEXLIST], slists[NKEXLIST]; + const struct kexinit_algorithm *selected[NKEXLIST]; + + BinarySource_BARE_INIT(client, client_kexinit.ptr, client_kexinit.len); + BinarySource_BARE_INIT(server, server_kexinit.ptr, server_kexinit.len); + + /* 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 (ptrlen_eq_string(found, kexlists[i][j].name)) { + selected[i] = &kexlists[i][j]; + break; + } + } + assert(selected[i]); /* kexlists[] must cover one of the inputs */ + + /* + * 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; + *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: + assert(false && "Bad list index in scan_kexinits"); + } + } + + 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; + + for (i = 0; i < lenof(ssh2_hostkey_algs); i++) + if (in_commasep_string(ssh2_hostkey_algs[i].alg->ssh_id, + slists[KEXLIST_HOSTKEY].ptr, + slists[KEXLIST_HOSTKEY].len)) + server_hostkeys[(*n_server_hostkeys)++] = i; + } + + 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. */ + ssh2_transport_filter_queue(s); + + 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. + */ + s->client_kexinit->len = 0; + put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); + { + int i; + for (i = 0; i < 16; i++) + put_byte(s->outgoing_kexinit, (unsigned char) random_byte()); + } + ssh2_write_kexinit_lists( + BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, + s->conf, 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; + } + s->incoming_kexinit->len = 0; + 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->out, &s->in, + &s->warn_kex, &s->warn_hk, &s->warn_cscipher, + &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks)) + 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 = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "key-exchange algorithm", s->kex_alg->name, + ssh2_transport_dialog_callback, s); + 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, + (const char *)NULL); + 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 = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "host key type", s->hostkey_alg->ssh_id, + ssh2_transport_dialog_callback, s); + } + 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 = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "client-to-server cipher", s->out.cipher->name, + ssh2_transport_dialog_callback, s); + 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 = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "server-to-client cipher", s->in.cipher->name, + ssh2_transport_dialog_callback, s); + 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) { + ssh2kex_coroutine(s); + if (!s->crStateKex) + break; + 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. */ + s->stats->out.running = true; + s->stats->out.remaining = 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(); + strbuf *cipher_iv = strbuf_new(); + strbuf *mac_key = strbuf_new(); + + 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); + } + + /* + * 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. */ + s->stats->in.running = true; + s->stats->in.remaining = s->max_data_size; + + /* + * We've seen incoming NEWKEYS, so create and initialise + * incoming session keys. + */ + { + strbuf *cipher_key = strbuf_new(); + strbuf *cipher_iv = strbuf_new(); + strbuf *mac_key = strbuf_new(); + + 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. + */ + freebn(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.running) { + s->rekey_reason = "too much data received"; + s->rekey_class = RK_NORMAL; + } else if (!s->stats->out.running) { + 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. */ + s->stats->in.running = s->stats->out.running = true; + s->stats->in.remaining = s->stats->out.remaining = + 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; +} + +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); +} + +#endif /* NO_GSSAPI */ + +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; + + 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->hostkey_alg = ssh2_hostkey_algs[arg].alg; + s->cross_certifying = true; + 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; + + /* We must decrement both counters, so avoid short-circuit + * evaluation skipping one */ + bool out_expired = DTS_CONSUME(s->stats, out, diff); + bool in_expired = DTS_CONSUME(s->stats, in, diff); + if (out_expired || 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); +} diff --git a/ssh2transport.h b/ssh2transport.h new file mode 100644 index 00000000..de6a6fe1 --- /dev/null +++ b/ssh2transport.h @@ -0,0 +1,228 @@ +/* + * 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 + +enum kexlist { + KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER, + KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP, + NKEXLIST +}; +#define MAXKEXLIST 16 +struct kexinit_algorithm { + const char *name; + union { + struct { + const struct ssh_kex *kex; + bool warn; + } kex; + struct { + const ssh_keyalg *hostkey; + bool warn; + } hk; + struct { + const struct ssh2_cipheralg *cipher; + bool warn; + } cipher; + struct { + const struct ssh2_macalg *mac; + bool etm; + } mac; + struct { + const struct ssh_compression_alg *comp; + bool delayed; + } comp; + } u; +}; + +#define HOSTKEY_ALGORITHMS(X) \ + X(HK_ED25519, ssh_ecdsa_ed25519) \ + 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) \ + /* 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 struct ssh2_cipheralg *cipher; + const struct ssh2_macalg *mac; + bool etm_mode; + const struct 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 struct ssh_kex *kex_alg; + const ssh_keyalg *hostkey_alg; + char *hostkey_str; /* string representation, for easy checking in rekeys */ + unsigned char session_id[SSH2_KEX_MAX_HASH_LEN]; + int session_id_len; + int dh_min_size, dh_max_size; + bool dh_got_size_bounds; + struct dh_ctx *dh_ctx; + ssh_hash *exhash; + + struct DataTransferStats *stats; + + 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; + Bignum 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; + ptrlen hostkeydata, sigdata; + strbuf *hostkeyblob; + char *keystr, *fingerprint; + ssh_key *hkey; /* actual host key */ + struct RSAKey *rsa_kex_key; /* for RSA kex */ + struct ec_key *ecdh_key; /* for ECDH kex */ + unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN]; + bool can_gssapi_keyex; + bool need_gss_transient_hostkey; + bool warned_about_no_gss_transient_hostkey; + bool got_session_id; + 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 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; + + /* + * Flag indicating that the current rekey is intended to finish + * with a newly cross-certified host key. + */ + bool 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 */ +void ssh2kex_coroutine(struct ssh2_transport_state *s); + +#endif /* PUTTY_SSH2TRANSPORT_H */ diff --git a/ssh2userauth-server.c b/ssh2userauth-server.c new file mode 100644 index 00000000..d28176ea --- /dev/null +++ b/ssh2userauth-server.c @@ -0,0 +1,364 @@ +/* + * 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; + + 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 struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = { + ssh2_userauth_server_free, + ssh2_userauth_server_process_queue, + NULL /* get_specials */, + NULL /* special_cmd */, + NULL /* want_user_input */, + NULL /* got_user_input */, + NULL /* reconfigure */, + "ssh-userauth", +}; + +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) +{ + 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; + + 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_data(sigdata, s->session_id.ptr, s->session_id.len); + } 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); + + 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; + 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); + + if (!auth_publickey(s->authpolicy, s->username, blob)) + goto failure; + + keyalg = find_pubkey_alg_len(algorithm); + if (!keyalg) + goto failure; + key = ssh_key_new_pub(keyalg, blob); + if (!key) + goto failure; + + if (!has_signature) { + ssh_key_free(key); + 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 */ + } + + 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 new file mode 100644 index 00000000..8e4b88dc --- /dev/null +++ b/ssh2userauth.c @@ -0,0 +1,1669 @@ +/* + * 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 + +struct ssh2_userauth_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + Filename *keyfile; + bool 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 kbd_inter_refused; + prompts_t *cur_prompt; + int num_prompts; + char *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 pkblob_pos_in_agent; + int keyi, nkeys; + ptrlen pk, alg, comment; + int len; + PktOut *pktout; + bool want_user_input; + + agent_pending_query *auth_agent_query; + bufchain banner; + + 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 const struct PacketProtocolLayerVtable ssh2_userauth_vtable = { + ssh2_userauth_free, + ssh2_userauth_process_queue, + ssh2_userauth_get_specials, + ssh2_userauth_special_cmd, + ssh2_userauth_want_user_input, + ssh2_userauth_got_user_input, + ssh2_userauth_reconfigure, + "ssh-userauth", +}; + +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, 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->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; + bufchain_init(&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); + + 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->hostname); + sfree(s->fullhostname); + 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: + string = get_string(pktin); + if (string.len > BANNER_LIMIT - bufchain_size(&s->banner)) + string.len = BANNER_LIMIT - bufchain_size(&s->banner); + sanitise_term_data(&s->banner, string.ptr, string.len); + 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 \"%.150s\"", + 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 (ssh2_userkey_loadpub(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 = + ssh2_userkey_encrypted(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). + */ + s->nkeys = 0; + s->pkblob_pos_in_agent = 0; + 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( + s->asrc, s->agent_response.ptr, s->agent_response.len); + + get_uint32(s->asrc); /* skip length field */ + if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { + int keyi; + + s->nkeys = toint(get_uint32(s->asrc)); + + /* + * Vet the Pageant response to ensure that the key count + * and blob lengths make sense. + */ + if (s->nkeys < 0) { + ppl_logevent(("Pageant response contained a negative" + " key count %d", s->nkeys)); + s->nkeys = 0; + goto done_agent_query; + } else { + ppl_logevent(("Pageant has %d SSH-2 keys", s->nkeys)); + + /* See if configured key is in agent. */ + for (keyi = 0; keyi < s->nkeys; keyi++) { + size_t pos = s->asrc->pos; + ptrlen blob = get_string(s->asrc); + get_string(s->asrc); /* skip comment */ + if (get_err(s->asrc)) { + ppl_logevent(("Pageant response was truncated")); + s->nkeys = 0; + goto done_agent_query; + } + + if (s->publickey_blob && + blob.len == s->publickey_blob->len && + !memcmp(blob.ptr, s->publickey_blob->s, + s->publickey_blob->len)) { + ppl_logevent(("Pageant key #%d matches " + "configured key file", keyi)); + s->keyi = keyi; + s->pkblob_pos_in_agent = pos; + break; + } + } + if (s->publickey_blob && !s->pkblob_pos_in_agent) { + ppl_logevent(("Configured key file not in Pageant")); + s->nkeys = 0; + } + } + } 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->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; + } + s->username = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) + 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; + + /* Reset agent request state. */ + s->done_agent = false; + if (s->agent_response.ptr) { + if (s->pkblob_pos_in_agent) { + s->asrc->pos = s->pkblob_pos_in_agent; + } else { + s->asrc->pos = 9; /* skip length + type + key count */ + s->keyi = 0; + } + } + + while (1) { + ptrlen methods; + + methods.ptr = ""; + methods.len = 0; + + /* + * Wait for the result of the last authentication request. + */ + 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, so we can safely pass it straight to + * seat_stderr. + */ + if (bufchain_size(&s->banner) && + (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) { + while (bufchain_size(&s->banner) > 0) { + void *data; + int len; + bufchain_prefix(&s->banner, &data, &len); + seat_stderr(s->ppl.seat, data, len); + bufchain_consume(&s->banner, len); + } + } + bufchain_clear(&s->banner); + } + if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { + ppl_logevent(("Access granted")); + goto userauth_success; + } + + if (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->type == SSH2_MSG_USERAUTH_FAILURE) { + methods = get_string(pktin); + if (!get_bool(pktin)) { + /* + * 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")); + } + + s->can_pubkey = + in_commasep_string("publickey", methods.ptr, methods.len); + s->can_passwd = + in_commasep_string("password", methods.ptr, methods.len); + s->can_keyb_inter = + s->try_ki_auth && + in_commasep_string("keyboard-interactive", + methods.ptr, methods.len); +#ifndef NO_GSSAPI + s->can_gssapi = + s->try_gssapi_auth && + in_commasep_string("gssapi-with-mic", + methods.ptr, methods.len) && + s->shgss->libs->nlibraries > 0; + s->can_gssapi_keyex_auth = + s->try_gssapi_kex_auth && + in_commasep_string("gssapi-keyex", + methods.ptr, methods.len) && + 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->nkeys) { + + /* + * Attempt public-key authentication using a key from Pageant. + */ + + s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; + + ppl_logevent(("Trying Pageant key #%d", s->keyi)); + + /* Unpack key from agent response */ + s->pk = get_string(s->asrc); + s->comment = get_string(s->asrc); + { + BinarySource src[1]; + BinarySource_BARE_INIT(src, s->pk.ptr, s->pk.len); + s->alg = get_string(src); + } + + /* 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->alg); + put_stringpl(s->pktout, s->pk); + 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; + + if (flags & FLAG_VERBOSE) + ppl_printf(("Authenticating with public key " + "\"%.*s\" from agent\r\n", + PTRLEN_PRINTF(s->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->alg); + put_stringpl(s->pktout, s->pk); + + /* Ask agent for signature. */ + agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); + put_stringpl(agentreq, s->pk); + /* 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 (zero) flags word. */ + put_uint32(agentreq, 0); + 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, + s->pk, sigblob); + pq_push(s->ppl.out_pq, s->pktout); + s->type = AUTH_TYPE_PUBLICKEY; + } else { + /* FIXME: less drastic response */ + ssh_sw_abort(s->ppl.ssh, "Pageant failed to " + "provide a signature"); + return; + } + } + } + + /* Do we have any keys left to try? */ + if (s->pkblob_pos_in_agent) { + s->done_agent = true; + s->tried_pubkey_config = true; + } else { + s->keyi++; + if (s->keyi >= s->nkeys) + s->done_agent = true; + } + + } else if (s->can_pubkey && s->publickey_blob && + s->privatekey_available && !s->tried_pubkey_config) { + + struct 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, 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 (flags & FLAG_VERBOSE) + 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->name = dupstr("SSH key passphrase"); + add_prompt(s->cur_prompt, + dupprintf("Passphrase for key \"%.100s\": ", + 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 = + dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + } else { + passphrase = NULL; /* no passphrase needed */ + } + + /* + * Try decrypting the key. + */ + key = ssh2_load_userkey(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; + 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, ssh_key_ssh_id(key->key)); + 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, sigdata->s, sigdata->len, + 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")); + 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_TOKEN) { + ppl_logevent(("GSSAPI authentication -" + " bad server response")); + s->gss_stat = SSH_GSS_FAILURE; + pq_push_front(s->ppl.in_pq, pktin); + 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")); + + 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; + } + + /* + * Loop while the server continues to send INFO_REQUESTs. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + + ptrlen name, inst; + int i; + + /* + * 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; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = get_uint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + ptrlen prompt; + bool echo; + static char noprompt[] = + ": "; + + prompt = get_string(pktin); + echo = get_bool(pktin); + if (!prompt.len) { + prompt.ptr = noprompt; + prompt.len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, mkstr(prompt), echo); + } + + if (name.len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = + dupprintf("SSH server: %.*s", PTRLEN_PRINTF(name)); + s->cur_prompt->name_reqd = true; + } else { + s->cur_prompt->name = + dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = false; + } + /* We add a prefix to try to make it clear that a prompt + * has come from the server. + * FIXME: ugly to print "Using..." in prompt _every_ + * time round. Can this be done more subtly? */ + /* Special case: for reasons best known to themselves, + * some servers send k-i requests with no prompts and + * nothing to display. Keep quiet in this case. */ + if (s->num_prompts || name.len || inst.len) { + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive " + "authentication.%s%.*s", + inst.len ? "\n" : "", + PTRLEN_PRINTF(inst)); + s->cur_prompt->instr_reqd = true; + } else { + s->cur_prompt->instr_reqd = false; + } + + /* + * Display any instructions, and 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 (i=0; i < s->num_prompts; i++) { + put_stringz(s->pktout, + s->cur_prompt->prompts[i]->result); + } + 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); + + } + + /* + * 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->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 = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + + /* + * Send the password packet. + * + * We pad out the password packet to 256 bytes to make + * it harder for an attacker to find the length of the + * user's password. + * + * Anyone using a password longer than 256 bytes + * probably doesn't have much to worry about from + * people who find out how long their password is! + */ + s->pktout = 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->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[0]) { + smemclr(s->password, strlen(s->password)); + /* burn the evidence */ + sfree(s->password); + s->password = + dupstr(s->cur_prompt->prompts[0]->result); + } + + /* + * Check the two new passwords match. + */ + got_new = (strcmp(s->cur_prompt->prompts[1]->result, + s->cur_prompt->prompts[2]->result) + == 0); + if (!got_new) + /* They don't. Silly user. */ + 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, + s->cur_prompt->prompts[1]->result); + 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)", + PTRLEN_PRINTF(methods)); + 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_data(sigdata, s->session_id.ptr, s->session_id.len); + } 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(pk, pkblob.ptr, pkblob.len); + BinarySource_BARE_INIT(sig, sigblob.ptr, sigblob.len); + + /* 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_data(substr, sig_mp.ptr, sig_mp.len); + 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); +} diff --git a/sshaes.c b/sshaes.c index 904cbdb2..fad9ff4f 100644 --- a/sshaes.c +++ b/sshaes.c @@ -1,5 +1,5 @@ /* - * aes.c - implementation of AES / Rijndael + * sshaes.c - implementation of AES / Rijndael * * AES is a flexible algorithm as regards endianness: it has no * inherent preference as to which way round you should form words @@ -33,22 +33,59 @@ #include "ssh.h" #define MAX_NR 14 /* max no of rounds */ -#define MAX_NK 8 /* max no of words in input key */ -#define MAX_NB 8 /* max no of words in cipher blk */ +#define NB 4 /* no of words in cipher blk */ #define mulby2(x) ( ((x&0x7F) << 1) ^ (x & 0x80 ? 0x1B : 0) ) -typedef struct AESContext AESContext; +/* + * Select appropriate inline keyword for the compiler + */ +#if defined __GNUC__ || defined __clang__ +# define INLINE __inline__ +#elif defined (_MSC_VER) +# define INLINE __forceinline +#else +# define INLINE +#endif struct AESContext { - word32 keysched[(MAX_NR + 1) * MAX_NB]; - word32 invkeysched[(MAX_NR + 1) * MAX_NB]; - void (*encrypt) (AESContext * ctx, word32 * block); - void (*decrypt) (AESContext * ctx, word32 * block); - word32 iv[MAX_NB]; - int Nb, Nr; + uint32_t keysched_buf[(MAX_NR + 1) * NB + 3]; + uint32_t invkeysched_buf[(MAX_NR + 1) * NB + 3]; + uint32_t *keysched, *invkeysched; + uint32_t iv[NB]; + int Nr; /* number of rounds */ + void (*encrypt_cbc)(unsigned char*, int, AESContext*); + void (*decrypt_cbc)(unsigned char*, int, AESContext*); + void (*sdctr)(unsigned char*, int, AESContext*); + bool isNI; }; +static void aes_encrypt_cbc_sw(unsigned char*, int, AESContext*); +static void aes_decrypt_cbc_sw(unsigned char*, int, AESContext*); +static void aes_sdctr_sw(unsigned char*, int, AESContext*); + +INLINE static bool supports_aes_ni(); +static void aes_setup_ni(AESContext * ctx, + const unsigned char *key, int keylen); + +INLINE static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + ctx->encrypt_cbc(blk, len, ctx); +} + +INLINE static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +{ + ctx->decrypt_cbc(blk, len, ctx); +} + +INLINE static void aes_sdctr(unsigned char *blk, int len, AESContext * ctx) +{ + ctx->sdctr(blk, len, ctx); +} + +/* + * SW AES lookup tables + */ static const unsigned char Sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, @@ -119,7 +156,7 @@ static const unsigned char Sboxinv[256] = { 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; -static const word32 E0[256] = { +static const uint32_t E0[256] = { 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, @@ -185,7 +222,7 @@ static const word32 E0[256] = { 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, }; -static const word32 E1[256] = { +static const uint32_t E1[256] = { 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, @@ -251,7 +288,7 @@ static const word32 E1[256] = { 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616, }; -static const word32 E2[256] = { +static const uint32_t E2[256] = { 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, @@ -317,7 +354,7 @@ static const word32 E2[256] = { 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16, }; -static const word32 E3[256] = { +static const uint32_t E3[256] = { 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, @@ -383,7 +420,7 @@ static const word32 E3[256] = { 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c, }; -static const word32 D0[256] = { +static const uint32_t D0[256] = { 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, @@ -449,7 +486,7 @@ static const word32 D0[256] = { 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742, }; -static const word32 D1[256] = { +static const uint32_t D1[256] = { 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, @@ -515,7 +552,7 @@ static const word32 D1[256] = { 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857, }; -static const word32 D2[256] = { +static const uint32_t D2[256] = { 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, @@ -581,7 +618,7 @@ static const word32 D2[256] = { 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8, }; -static const word32 D3[256] = { +static const uint32_t D3[256] = { 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, @@ -649,317 +686,47 @@ static const word32 D3[256] = { }; /* - * Common macros in both the encryption and decryption routines. - */ -#define ADD_ROUND_KEY_4 (block[0]^=*keysched++, block[1]^=*keysched++, \ - block[2]^=*keysched++, block[3]^=*keysched++) -#define ADD_ROUND_KEY_6 (block[0]^=*keysched++, block[1]^=*keysched++, \ - block[2]^=*keysched++, block[3]^=*keysched++, \ - block[4]^=*keysched++, block[5]^=*keysched++) -#define ADD_ROUND_KEY_8 (block[0]^=*keysched++, block[1]^=*keysched++, \ - block[2]^=*keysched++, block[3]^=*keysched++, \ - block[4]^=*keysched++, block[5]^=*keysched++, \ - block[6]^=*keysched++, block[7]^=*keysched++) -#define MOVEWORD(i) ( block[i] = newstate[i] ) - -/* - * Macros for the encryption routine. There are three encryption - * cores, for Nb=4,6,8. - */ -#define MAKEWORD(i) ( newstate[i] = (E0[(block[i] >> 24) & 0xFF] ^ \ - E1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ - E2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ - E3[block[(i+C3)%Nb] & 0xFF]) ) -#define LASTWORD(i) ( newstate[i] = (Sbox[(block[i] >> 24) & 0xFF] << 24) | \ - (Sbox[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ - (Sbox[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ - (Sbox[(block[(i+C3)%Nb] ) & 0xFF] ) ) - -/* - * Core encrypt routines, expecting word32 inputs read big-endian - * from the byte-oriented input stream. + * Set up an AESContext. `keylen' is measured in + * bytes; it can be either 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). */ -static void aes_encrypt_nb_4(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 1, C2 = 2, C3 = 3, Nb = 4; - word32 *keysched = ctx->keysched; - word32 newstate[4]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_4; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - } - ADD_ROUND_KEY_4; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - ADD_ROUND_KEY_4; -} -static void aes_encrypt_nb_6(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 1, C2 = 2, C3 = 3, Nb = 6; - word32 *keysched = ctx->keysched; - word32 newstate[6]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_6; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - } - ADD_ROUND_KEY_6; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - ADD_ROUND_KEY_6; -} -static void aes_encrypt_nb_8(AESContext * ctx, word32 * block) +static void aes_setup(AESContext * ctx, const unsigned char *key, int keylen) { - int i; - static const int C1 = 1, C2 = 3, C3 = 4, Nb = 8; - word32 *keysched = ctx->keysched; - word32 newstate[8]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_8; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MAKEWORD(6); - MAKEWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - } - ADD_ROUND_KEY_8; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - LASTWORD(6); - LASTWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - ADD_ROUND_KEY_8; -} - -#undef MAKEWORD -#undef LASTWORD - -/* - * Macros for the decryption routine. There are three decryption - * cores, for Nb=4,6,8. - */ -#define MAKEWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \ - D1[(block[(i+C1)%Nb] >> 16) & 0xFF] ^ \ - D2[(block[(i+C2)%Nb] >> 8) & 0xFF] ^ \ - D3[block[(i+C3)%Nb] & 0xFF]) ) -#define LASTWORD(i) (newstate[i] = (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \ - (Sboxinv[(block[(i+C1)%Nb] >> 16) & 0xFF] << 16) | \ - (Sboxinv[(block[(i+C2)%Nb] >> 8) & 0xFF] << 8) | \ - (Sboxinv[(block[(i+C3)%Nb] ) & 0xFF] ) ) + int i, j, Nk, rconst; + size_t bufaddr; -/* - * Core decrypt routines, expecting word32 inputs read big-endian - * from the byte-oriented input stream. - */ -static void aes_decrypt_nb_4(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 4 - 1, C2 = 4 - 2, C3 = 4 - 3, Nb = 4; - word32 *keysched = ctx->invkeysched; - word32 newstate[4]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_4; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - } - ADD_ROUND_KEY_4; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - ADD_ROUND_KEY_4; -} -static void aes_decrypt_nb_6(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 6 - 1, C2 = 6 - 2, C3 = 6 - 3, Nb = 6; - word32 *keysched = ctx->invkeysched; - word32 newstate[6]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_6; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - } - ADD_ROUND_KEY_6; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - ADD_ROUND_KEY_6; -} -static void aes_decrypt_nb_8(AESContext * ctx, word32 * block) -{ - int i; - static const int C1 = 8 - 1, C2 = 8 - 3, C3 = 8 - 4, Nb = 8; - word32 *keysched = ctx->invkeysched; - word32 newstate[8]; - for (i = 0; i < ctx->Nr - 1; i++) { - ADD_ROUND_KEY_8; - MAKEWORD(0); - MAKEWORD(1); - MAKEWORD(2); - MAKEWORD(3); - MAKEWORD(4); - MAKEWORD(5); - MAKEWORD(6); - MAKEWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - } - ADD_ROUND_KEY_8; - LASTWORD(0); - LASTWORD(1); - LASTWORD(2); - LASTWORD(3); - LASTWORD(4); - LASTWORD(5); - LASTWORD(6); - LASTWORD(7); - MOVEWORD(0); - MOVEWORD(1); - MOVEWORD(2); - MOVEWORD(3); - MOVEWORD(4); - MOVEWORD(5); - MOVEWORD(6); - MOVEWORD(7); - ADD_ROUND_KEY_8; -} + ctx->Nr = 6 + (keylen / 4); /* Number of rounds */ -#undef MAKEWORD -#undef LASTWORD + /* Ensure the key schedule arrays are 16-byte aligned */ + bufaddr = (size_t)ctx->keysched_buf; + ctx->keysched = ctx->keysched_buf + + (0xF & -bufaddr) / sizeof(uint32_t); + assert((size_t)ctx->keysched % 16 == 0); + bufaddr = (size_t)ctx->invkeysched_buf; + ctx->invkeysched = ctx->invkeysched_buf + + (0xF & -bufaddr) / sizeof(uint32_t); + assert((size_t)ctx->invkeysched % 16 == 0); + ctx->isNI = supports_aes_ni(); -/* - * Set up an AESContext. `keylen' and `blocklen' are measured in - * bytes; each can be either 16 (128-bit), 24 (192-bit), or 32 - * (256-bit). - */ -static void aes_setup(AESContext * ctx, int blocklen, - unsigned char *key, int keylen) -{ - int i, j, Nk, rconst; + if (ctx->isNI) { + aes_setup_ni(ctx, key, keylen); + return; + } - assert(blocklen == 16 || blocklen == 24 || blocklen == 32); assert(keylen == 16 || keylen == 24 || keylen == 32); - /* - * Basic parameters. Words per block, words in key, rounds. - */ - Nk = keylen / 4; - ctx->Nb = blocklen / 4; - ctx->Nr = 6 + (ctx->Nb > Nk ? ctx->Nb : Nk); - - /* - * Assign core-function pointers. - */ - if (ctx->Nb == 8) - ctx->encrypt = aes_encrypt_nb_8, ctx->decrypt = aes_decrypt_nb_8; - else if (ctx->Nb == 6) - ctx->encrypt = aes_encrypt_nb_6, ctx->decrypt = aes_decrypt_nb_6; - else if (ctx->Nb == 4) - ctx->encrypt = aes_encrypt_nb_4, ctx->decrypt = aes_decrypt_nb_4; + ctx->encrypt_cbc = aes_encrypt_cbc_sw; + ctx->decrypt_cbc = aes_decrypt_cbc_sw; + ctx->sdctr = aes_sdctr_sw; - /* - * Now do the key setup itself. - */ + Nk = keylen / 4; rconst = 1; - for (i = 0; i < (ctx->Nr + 1) * ctx->Nb; i++) { + for (i = 0; i < (ctx->Nr + 1) * NB; i++) { if (i < Nk) ctx->keysched[i] = GET_32BIT_MSB_FIRST(key + 4 * i); else { - word32 temp = ctx->keysched[i - 1]; + uint32_t temp = ctx->keysched[i - 1]; if (i % Nk == 0) { int a, b, c, d; a = (temp >> 16) & 0xFF; @@ -990,9 +757,9 @@ static void aes_setup(AESContext * ctx, int blocklen, * Now prepare the modified keys for the inverse cipher. */ for (i = 0; i <= ctx->Nr; i++) { - for (j = 0; j < ctx->Nb; j++) { - word32 temp; - temp = ctx->keysched[(ctx->Nr - i) * ctx->Nb + j]; + for (j = 0; j < NB; j++) { + uint32_t temp; + temp = ctx->keysched[(ctx->Nr - i) * NB + j]; if (i != 0 && i != ctx->Nr) { /* * Perform the InvMixColumn operation on i. The D @@ -1010,222 +777,375 @@ static void aes_setup(AESContext * ctx, int blocklen, temp ^= D2[Sbox[c]]; temp ^= D3[Sbox[d]]; } - ctx->invkeysched[i * ctx->Nb + j] = temp; + ctx->invkeysched[i * NB + j] = temp; } } } -static void aes_encrypt(AESContext * ctx, word32 * block) -{ - ctx->encrypt(ctx, block); -} +/* + * Software encrypt/decrypt macros + */ +#define ADD_ROUND_KEY (block[0]^=*keysched++, \ + block[1]^=*keysched++, \ + block[2]^=*keysched++, \ + block[3]^=*keysched++) +#define MOVEWORD(i) ( block[i] = newstate[i] ) -static void aes_decrypt(AESContext * ctx, word32 * block) -{ - ctx->decrypt(ctx, block); -} +#define ENCWORD(i) ( newstate[i] = (E0[(block[i ] >> 24) & 0xFF] ^ \ + E1[(block[(i+1)%NB] >> 16) & 0xFF] ^ \ + E2[(block[(i+2)%NB] >> 8) & 0xFF] ^ \ + E3[ block[(i+3)%NB] & 0xFF]) ) +#define ENCROUND { ENCWORD(0); ENCWORD(1); ENCWORD(2); ENCWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } -static void aes_encrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +#define ENCLASTWORD(i) ( newstate[i] = \ + (Sbox[(block[i] >> 24) & 0xFF] << 24) | \ + (Sbox[(block[(i+1)%NB] >> 16) & 0xFF] << 16) | \ + (Sbox[(block[(i+2)%NB] >> 8) & 0xFF] << 8) | \ + (Sbox[(block[(i+3)%NB] ) & 0xFF] ) ) +#define ENCLASTROUND { ENCLASTWORD(0); ENCLASTWORD(1); ENCLASTWORD(2); ENCLASTWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } + +#define DECWORD(i) ( newstate[i] = (D0[(block[i] >> 24) & 0xFF] ^ \ + D1[(block[(i+3)%NB] >> 16) & 0xFF] ^ \ + D2[(block[(i+2)%NB] >> 8) & 0xFF] ^ \ + D3[ block[(i+1)%NB] & 0xFF]) ) +#define DECROUND { DECWORD(0); DECWORD(1); DECWORD(2); DECWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } + +#define DECLASTWORD(i) (newstate[i] = \ + (Sboxinv[(block[i] >> 24) & 0xFF] << 24) | \ + (Sboxinv[(block[(i+3)%NB] >> 16) & 0xFF] << 16) | \ + (Sboxinv[(block[(i+2)%NB] >> 8) & 0xFF] << 8) | \ + (Sboxinv[(block[(i+1)%NB] ) & 0xFF] ) ) +#define DECLASTROUND { DECLASTWORD(0); DECLASTWORD(1); DECLASTWORD(2); DECLASTWORD(3); \ + MOVEWORD(0); MOVEWORD(1); MOVEWORD(2); MOVEWORD(3); ADD_ROUND_KEY; } + +/* + * Software AES encrypt/decrypt core + */ +static void aes_encrypt_cbc_sw(unsigned char *blk, int len, AESContext * ctx) { - word32 iv[4]; + uint32_t block[4]; + unsigned char* finish = blk + len; int i; assert((len & 15) == 0); - memcpy(iv, ctx->iv, sizeof(iv)); + memcpy(block, ctx->iv, sizeof(block)); - while (len > 0) { + while (blk < finish) { + uint32_t *keysched = ctx->keysched; + uint32_t newstate[4]; for (i = 0; i < 4; i++) - iv[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i); - aes_encrypt(ctx, iv); + block[i] ^= GET_32BIT_MSB_FIRST(blk + 4 * i); + ADD_ROUND_KEY; + switch (ctx->Nr) { + case 14: + ENCROUND; + ENCROUND; + case 12: + ENCROUND; + ENCROUND; + case 10: + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCLASTROUND; + break; + default: + assert(0); + } for (i = 0; i < 4; i++) - PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i]); + PUT_32BIT_MSB_FIRST(blk + 4 * i, block[i]); blk += 16; - len -= 16; } - memcpy(ctx->iv, iv, sizeof(iv)); + memcpy(ctx->iv, block, sizeof(block)); } -static void aes_decrypt_cbc(unsigned char *blk, int len, AESContext * ctx) +static void aes_sdctr_sw(unsigned char *blk, int len, AESContext *ctx) { - word32 iv[4], x[4], ct[4]; + uint32_t iv[4]; + unsigned char* finish = blk + len; int i; assert((len & 15) == 0); memcpy(iv, ctx->iv, sizeof(iv)); - while (len > 0) { - for (i = 0; i < 4; i++) - x[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i); - aes_decrypt(ctx, x); + while (blk < finish) { + uint32_t *keysched = ctx->keysched; + uint32_t newstate[4], block[4], tmp; + memcpy(block, iv, sizeof(block)); + ADD_ROUND_KEY; + switch (ctx->Nr) { + case 14: + ENCROUND; + ENCROUND; + case 12: + ENCROUND; + ENCROUND; + case 10: + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCROUND; + ENCLASTROUND; + break; + default: + assert(0); + } for (i = 0; i < 4; i++) { - PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ x[i]); - iv[i] = ct[i]; + tmp = GET_32BIT_MSB_FIRST(blk + 4 * i); + PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ block[i]); } + for (i = 3; i >= 0; i--) + if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0) + break; blk += 16; - len -= 16; } memcpy(ctx->iv, iv, sizeof(iv)); } -static void aes_sdctr(unsigned char *blk, int len, AESContext *ctx) +static void aes_decrypt_cbc_sw(unsigned char *blk, int len, AESContext * ctx) { - word32 iv[4], b[4], tmp; + uint32_t iv[4]; + unsigned char* finish = blk + len; int i; assert((len & 15) == 0); memcpy(iv, ctx->iv, sizeof(iv)); - while (len > 0) { - memcpy(b, iv, sizeof(b)); - aes_encrypt(ctx, b); + while (blk < finish) { + uint32_t *keysched = ctx->invkeysched; + uint32_t newstate[4], ct[4], block[4]; + for (i = 0; i < 4; i++) + block[i] = ct[i] = GET_32BIT_MSB_FIRST(blk + 4 * i); + ADD_ROUND_KEY; + switch (ctx->Nr) { + case 14: + DECROUND; + DECROUND; + case 12: + DECROUND; + DECROUND; + case 10: + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECROUND; + DECLASTROUND; + break; + default: + assert(0); + } for (i = 0; i < 4; i++) { - tmp = GET_32BIT_MSB_FIRST(blk + 4 * i); - PUT_32BIT_MSB_FIRST(blk + 4 * i, tmp ^ b[i]); + PUT_32BIT_MSB_FIRST(blk + 4 * i, iv[i] ^ block[i]); + iv[i] = ct[i]; } - for (i = 3; i >= 0; i--) - if ((iv[i] = (iv[i] + 1) & 0xffffffff) != 0) - break; blk += 16; - len -= 16; } memcpy(ctx->iv, iv, sizeof(iv)); } -void *aes_make_context(void) +AESContext *aes_make_context(void) { return snew(AESContext); } -void aes_free_context(void *handle) +void aes_free_context(AESContext *ctx) { - sfree(handle); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -void aes128_key(void *handle, unsigned char *key) +void aes128_key(AESContext *ctx, const void *key) { - AESContext *ctx = (AESContext *)handle; - aes_setup(ctx, 16, key, 16); + aes_setup(ctx, key, 16); } -void aes192_key(void *handle, unsigned char *key) +void aes192_key(AESContext *ctx, const void *key) { - AESContext *ctx = (AESContext *)handle; - aes_setup(ctx, 16, key, 24); + aes_setup(ctx, key, 24); } -void aes256_key(void *handle, unsigned char *key) +void aes256_key(AESContext *ctx, const void *key) { - AESContext *ctx = (AESContext *)handle; - aes_setup(ctx, 16, key, 32); + aes_setup(ctx, key, 32); } -void aes_iv(void *handle, unsigned char *iv) +void aes_iv(AESContext *ctx, const void *viv) { - AESContext *ctx = (AESContext *)handle; - int i; - for (i = 0; i < 4; i++) - ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i); + const unsigned char *iv = (const unsigned char *)viv; + if (ctx->isNI) { + memcpy(ctx->iv, iv, sizeof(ctx->iv)); + } + else { + int i; + for (i = 0; i < 4; i++) + ctx->iv[i] = GET_32BIT_MSB_FIRST(iv + 4 * i); + } } -void aes_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +void aes_ssh2_encrypt_blk(AESContext *ctx, void *blk, int len) { - AESContext *ctx = (AESContext *)handle; aes_encrypt_cbc(blk, len, ctx); } -void aes_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +void aes_ssh2_decrypt_blk(AESContext *ctx, void *blk, int len) { - AESContext *ctx = (AESContext *)handle; aes_decrypt_cbc(blk, len, ctx); } -static void aes_ssh2_sdctr(void *handle, unsigned char *blk, int len) +void aes_ssh2_sdctr(AESContext *ctx, void *blk, int len) { - AESContext *ctx = (AESContext *)handle; aes_sdctr(blk, len, ctx); } -void aes256_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +void aes256_encrypt_pubkey(const void *key, void *blk, int len) { AESContext ctx; - aes_setup(&ctx, 16, key, 32); + aes_setup(&ctx, key, 32); memset(ctx.iv, 0, sizeof(ctx.iv)); aes_encrypt_cbc(blk, len, &ctx); smemclr(&ctx, sizeof(ctx)); } -void aes256_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +void aes256_decrypt_pubkey(const void *key, void *blk, int len) { AESContext ctx; - aes_setup(&ctx, 16, key, 32); + aes_setup(&ctx, key, 32); memset(ctx.iv, 0, sizeof(ctx.iv)); aes_decrypt_cbc(blk, len, &ctx); smemclr(&ctx, sizeof(ctx)); } -static const struct ssh2_cipher ssh_aes128_ctr = { - aes_make_context, aes_free_context, aes_iv, aes128_key, - aes_ssh2_sdctr, aes_ssh2_sdctr, NULL, NULL, +struct aes_ssh2_ctx { + AESContext context; + ssh2_cipher vt; +}; + +ssh2_cipher *aes_ssh2_new(const struct ssh2_cipheralg *alg) +{ + struct aes_ssh2_ctx *ctx = snew(struct aes_ssh2_ctx); + ctx->vt = alg; + return &ctx->vt; +} + +static void aes_ssh2_free(ssh2_cipher *cipher) +{ + struct aes_ssh2_ctx *ctx = container_of(cipher, struct aes_ssh2_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void aes_ssh2_setiv(ssh2_cipher *cipher, const void *iv) +{ + struct aes_ssh2_ctx *ctx = container_of(cipher, struct aes_ssh2_ctx, vt); + aes_iv(&ctx->context, iv); +} + +static void aes_ssh2_setkey(ssh2_cipher *cipher, const void *key) +{ + struct aes_ssh2_ctx *ctx = container_of(cipher, struct aes_ssh2_ctx, vt); + aes_setup(&ctx->context, key, ctx->vt->padded_keybytes); +} + +static void aes_ssh2_encrypt(ssh2_cipher *cipher, void *blk, int len) +{ + struct aes_ssh2_ctx *ctx = container_of(cipher, struct aes_ssh2_ctx, vt); + aes_encrypt_cbc(blk, len, &ctx->context); +} + +static void aes_ssh2_decrypt(ssh2_cipher *cipher, void *blk, int len) +{ + struct aes_ssh2_ctx *ctx = container_of(cipher, struct aes_ssh2_ctx, vt); + aes_decrypt_cbc(blk, len, &ctx->context); +} + +static void aes_ssh2_sdctr_method(ssh2_cipher *cipher, void *blk, int len) +{ + struct aes_ssh2_ctx *ctx = container_of(cipher, struct aes_ssh2_ctx, vt); + aes_sdctr(blk, len, &ctx->context); +} + +static const struct ssh2_cipheralg ssh_aes128_ctr = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_sdctr_method, aes_ssh2_sdctr_method, NULL, NULL, "aes128-ctr", 16, 128, 16, 0, "AES-128 SDCTR", NULL }; -static const struct ssh2_cipher ssh_aes192_ctr = { - aes_make_context, aes_free_context, aes_iv, aes192_key, - aes_ssh2_sdctr, aes_ssh2_sdctr, NULL, NULL, +static const struct ssh2_cipheralg ssh_aes192_ctr = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_sdctr_method, aes_ssh2_sdctr_method, NULL, NULL, "aes192-ctr", 16, 192, 24, 0, "AES-192 SDCTR", NULL }; -static const struct ssh2_cipher ssh_aes256_ctr = { - aes_make_context, aes_free_context, aes_iv, aes256_key, - aes_ssh2_sdctr, aes_ssh2_sdctr, NULL, NULL, +static const struct ssh2_cipheralg ssh_aes256_ctr = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_sdctr_method, aes_ssh2_sdctr_method, NULL, NULL, "aes256-ctr", 16, 256, 32, 0, "AES-256 SDCTR", NULL }; -static const struct ssh2_cipher ssh_aes128 = { - aes_make_context, aes_free_context, aes_iv, aes128_key, - aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL, +static const struct ssh2_cipheralg ssh_aes128 = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_encrypt, aes_ssh2_decrypt, NULL, NULL, "aes128-cbc", 16, 128, 16, SSH_CIPHER_IS_CBC, "AES-128 CBC", NULL }; -static const struct ssh2_cipher ssh_aes192 = { - aes_make_context, aes_free_context, aes_iv, aes192_key, - aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL, +static const struct ssh2_cipheralg ssh_aes192 = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_encrypt, aes_ssh2_decrypt, NULL, NULL, "aes192-cbc", 16, 192, 24, SSH_CIPHER_IS_CBC, "AES-192 CBC", NULL }; -static const struct ssh2_cipher ssh_aes256 = { - aes_make_context, aes_free_context, aes_iv, aes256_key, - aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL, +static const struct ssh2_cipheralg ssh_aes256 = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_encrypt, aes_ssh2_decrypt, NULL, NULL, "aes256-cbc", 16, 256, 32, SSH_CIPHER_IS_CBC, "AES-256 CBC", NULL }; -static const struct ssh2_cipher ssh_rijndael_lysator = { - aes_make_context, aes_free_context, aes_iv, aes256_key, - aes_ssh2_encrypt_blk, aes_ssh2_decrypt_blk, NULL, NULL, +static const struct ssh2_cipheralg ssh_rijndael_lysator = { + aes_ssh2_new, aes_ssh2_free, aes_ssh2_setiv, aes_ssh2_setkey, + aes_ssh2_encrypt, aes_ssh2_decrypt, NULL, NULL, "rijndael-cbc@lysator.liu.se", 16, 256, 32, SSH_CIPHER_IS_CBC, "AES-256 CBC", NULL }; -static const struct ssh2_cipher *const aes_list[] = { +static const struct ssh2_cipheralg *const aes_list[] = { &ssh_aes256_ctr, &ssh_aes256, &ssh_rijndael_lysator, @@ -1239,3 +1159,605 @@ const struct ssh2_ciphers ssh2_aes = { sizeof(aes_list) / sizeof(*aes_list), aes_list }; + +/* + * Implementation of AES for PuTTY using AES-NI + * instuction set expansion was made by: + * @author Pavel Kryukov + * @author Maxim Kuznetsov + * @author Svyatoslav Kuzmich + * + * For Putty AES NI project + * http://pavelkryukov.github.io/putty-aes-ni/ + */ + +/* + * Check of compiler version + */ +#ifdef _FORCE_AES_NI +# define COMPILER_SUPPORTS_AES_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && (defined(__x86_64__) || defined(__i386)) +# define COMPILER_SUPPORTS_AES_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) && (defined(__x86_64__) || defined(__i386)) +# define COMPILER_SUPPORTS_AES_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define COMPILER_SUPPORTS_AES_NI +# endif +#endif + +#ifdef _FORCE_SOFTWARE_AES +# undef COMPILER_SUPPORTS_AES_NI +#endif + +#ifdef COMPILER_SUPPORTS_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 + +/* + * Determinators of CPU type + */ +#if defined(__clang__) || defined(__GNUC__) + +#include +INLINE static bool supports_aes_ni() +{ + unsigned int CPUInfo[4]; + __cpuid(1, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); + return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); /* Check AES and SSE4.1 */ +} + +#else /* defined(__clang__) || defined(__GNUC__) */ + +INLINE static bool supports_aes_ni() +{ + unsigned int CPUInfo[4]; + __cpuid(CPUInfo, 1); + return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); /* Check AES and SSE4.1 */ +} + +#endif /* defined(__clang__) || defined(__GNUC__) */ + +/* + * Wrapper of SHUFPD instruction for MSVC + */ +#ifdef _MSC_VER +FUNC_ISA +INLINE static __m128i mm_shuffle_pd_i0(__m128i a, __m128i b) +{ + union { + __m128i i; + __m128d d; + } au, bu, ru; + au.i = a; + bu.i = b; + ru.d = _mm_shuffle_pd(au.d, bu.d, 0); + return ru.i; +} + +FUNC_ISA +INLINE static __m128i mm_shuffle_pd_i1(__m128i a, __m128i b) +{ + union { + __m128i i; + __m128d d; + } au, bu, ru; + au.i = a; + bu.i = b; + ru.d = _mm_shuffle_pd(au.d, bu.d, 1); + return ru.i; +} +#else +#define mm_shuffle_pd_i0(a, b) ((__m128i)_mm_shuffle_pd((__m128d)a, (__m128d)b, 0)); +#define mm_shuffle_pd_i1(a, b) ((__m128i)_mm_shuffle_pd((__m128d)a, (__m128d)b, 1)); +#endif + +/* + * AES-NI key expansion assist functions + */ +FUNC_ISA +INLINE static __m128i AES_128_ASSIST (__m128i temp1, __m128i temp2) +{ + __m128i temp3; + temp2 = _mm_shuffle_epi32 (temp2 ,0xff); + temp3 = _mm_slli_si128 (temp1, 0x4); + temp1 = _mm_xor_si128 (temp1, temp3); + temp3 = _mm_slli_si128 (temp3, 0x4); + temp1 = _mm_xor_si128 (temp1, temp3); + temp3 = _mm_slli_si128 (temp3, 0x4); + temp1 = _mm_xor_si128 (temp1, temp3); + temp1 = _mm_xor_si128 (temp1, temp2); + return temp1; +} + +FUNC_ISA +INLINE static void KEY_192_ASSIST(__m128i* temp1, __m128i * temp2, __m128i * temp3) +{ + __m128i temp4; + *temp2 = _mm_shuffle_epi32 (*temp2, 0x55); + temp4 = _mm_slli_si128 (*temp1, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + *temp1 = _mm_xor_si128 (*temp1, *temp2); + *temp2 = _mm_shuffle_epi32(*temp1, 0xff); + temp4 = _mm_slli_si128 (*temp3, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + *temp3 = _mm_xor_si128 (*temp3, *temp2); +} + +FUNC_ISA +INLINE static void KEY_256_ASSIST_1(__m128i* temp1, __m128i * temp2) +{ + __m128i temp4; + *temp2 = _mm_shuffle_epi32(*temp2, 0xff); + temp4 = _mm_slli_si128 (*temp1, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp1 = _mm_xor_si128 (*temp1, temp4); + *temp1 = _mm_xor_si128 (*temp1, *temp2); +} + +FUNC_ISA +INLINE static void KEY_256_ASSIST_2(__m128i* temp1, __m128i * temp3) +{ + __m128i temp2,temp4; + temp4 = _mm_aeskeygenassist_si128 (*temp1, 0x0); + temp2 = _mm_shuffle_epi32(temp4, 0xaa); + temp4 = _mm_slli_si128 (*temp3, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + temp4 = _mm_slli_si128 (temp4, 0x4); + *temp3 = _mm_xor_si128 (*temp3, temp4); + *temp3 = _mm_xor_si128 (*temp3, temp2); +} + +/* + * AES-NI key expansion core + */ +FUNC_ISA +static void AES_128_Key_Expansion (const unsigned char *userkey, __m128i *key) +{ + __m128i temp1, temp2; + temp1 = _mm_loadu_si128((const __m128i*)userkey); + key[0] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1 ,0x1); + temp1 = AES_128_ASSIST(temp1, temp2); + key[1] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x2); + temp1 = AES_128_ASSIST(temp1, temp2); + key[2] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x4); + temp1 = AES_128_ASSIST(temp1, temp2); + key[3] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x8); + temp1 = AES_128_ASSIST(temp1, temp2); + key[4] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x10); + temp1 = AES_128_ASSIST(temp1, temp2); + key[5] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x20); + temp1 = AES_128_ASSIST(temp1, temp2); + key[6] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x40); + temp1 = AES_128_ASSIST(temp1, temp2); + key[7] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x80); + temp1 = AES_128_ASSIST(temp1, temp2); + key[8] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x1b); + temp1 = AES_128_ASSIST(temp1, temp2); + key[9] = temp1; + temp2 = _mm_aeskeygenassist_si128 (temp1,0x36); + temp1 = AES_128_ASSIST(temp1, temp2); + key[10] = temp1; +} + +FUNC_ISA +static void AES_192_Key_Expansion (const unsigned char *userkey, __m128i *key) +{ + __m128i temp1, temp2, temp3; + temp1 = _mm_loadu_si128((const __m128i*)userkey); + temp3 = _mm_loadu_si128((const __m128i*)(userkey+16)); + key[0]=temp1; + key[1]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x1); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[1] = mm_shuffle_pd_i0(key[1], temp1); + key[2] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x2); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[3]=temp1; + key[4]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x4); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[4] = mm_shuffle_pd_i0(key[4], temp1); + key[5] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x8); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[6]=temp1; + key[7]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x10); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[7] = mm_shuffle_pd_i0(key[7], temp1); + key[8] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x20); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[9]=temp1; + key[10]=temp3; + temp2=_mm_aeskeygenassist_si128 (temp3,0x40); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[10] = mm_shuffle_pd_i0(key[10], temp1); + key[11] = mm_shuffle_pd_i1(temp1, temp3); + temp2=_mm_aeskeygenassist_si128 (temp3,0x80); + KEY_192_ASSIST(&temp1, &temp2, &temp3); + key[12]=temp1; + key[13]=temp3; +} + +FUNC_ISA +static void AES_256_Key_Expansion (const unsigned char *userkey, __m128i *key) +{ + __m128i temp1, temp2, temp3; + temp1 = _mm_loadu_si128((const __m128i*)userkey); + temp3 = _mm_loadu_si128((const __m128i*)(userkey+16)); + key[0] = temp1; + key[1] = temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x01); + KEY_256_ASSIST_1(&temp1, &temp2); + key[2]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[3]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x02); + KEY_256_ASSIST_1(&temp1, &temp2); + key[4]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[5]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x04); + KEY_256_ASSIST_1(&temp1, &temp2); + key[6]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[7]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x08); + KEY_256_ASSIST_1(&temp1, &temp2); + key[8]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[9]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x10); + KEY_256_ASSIST_1(&temp1, &temp2); + key[10]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[11]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x20); + KEY_256_ASSIST_1(&temp1, &temp2); + key[12]=temp1; + KEY_256_ASSIST_2(&temp1, &temp3); + key[13]=temp3; + temp2 = _mm_aeskeygenassist_si128 (temp3,0x40); + KEY_256_ASSIST_1(&temp1, &temp2); + key[14]=temp1; +} + +/* + * AES-NI encrypt/decrypt core + */ +FUNC_ISA +static void aes_encrypt_cbc_ni(unsigned char *blk, int len, AESContext * ctx) +{ + __m128i enc; + __m128i* block = (__m128i*)blk; + const __m128i* finish = (__m128i*)(blk + len); + + assert((len & 15) == 0); + + /* Load IV */ + enc = _mm_loadu_si128((__m128i*)(ctx->iv)); + while (block < finish) { + /* Key schedule ptr */ + __m128i* keysched = (__m128i*)ctx->keysched; + + /* Xor data with IV */ + enc = _mm_xor_si128(_mm_loadu_si128(block), enc); + + /* Perform rounds */ + enc = _mm_xor_si128(enc, *keysched); + switch (ctx->Nr) { + case 14: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 12: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 10: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenclast_si128(enc, *(++keysched)); + break; + default: + assert(0); + } + + /* Store and go to next block */ + _mm_storeu_si128(block, enc); + ++block; + } + + /* Update IV */ + _mm_storeu_si128((__m128i*)(ctx->iv), enc); +} + +FUNC_ISA +static void aes_decrypt_cbc_ni(unsigned char *blk, int len, AESContext * ctx) +{ + __m128i dec = _mm_setzero_si128(); + __m128i last, iv; + __m128i* block = (__m128i*)blk; + const __m128i* finish = (__m128i*)(blk + len); + + assert((len & 15) == 0); + + /* Load IV */ + iv = _mm_loadu_si128((__m128i*)(ctx->iv)); + while (block < finish) { + /* Key schedule ptr */ + __m128i* keysched = (__m128i*)ctx->invkeysched; + last = _mm_loadu_si128(block); + dec = _mm_xor_si128(last, *keysched); + switch (ctx->Nr) { + case 14: + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + case 12: + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + case 10: + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdec_si128(dec, *(++keysched)); + dec = _mm_aesdeclast_si128(dec, *(++keysched)); + break; + default: + assert(0); + } + + /* Xor data with IV */ + dec = _mm_xor_si128(iv, dec); + + /* Store data */ + _mm_storeu_si128(block, dec); + iv = last; + + /* Go to next block */ + ++block; + } + + /* Update IV */ + _mm_storeu_si128((__m128i*)(ctx->iv), iv); +} + +FUNC_ISA +static void aes_sdctr_ni(unsigned char *blk, int len, AESContext *ctx) +{ + const __m128i BSWAP_EPI64 = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); + const __m128i ONE = _mm_setr_epi32(0,0,0,1); + const __m128i ZERO = _mm_setzero_si128(); + __m128i iv; + __m128i* block = (__m128i*)blk; + const __m128i* finish = (__m128i*)(blk + len); + + assert((len & 15) == 0); + + iv = _mm_loadu_si128((__m128i*)ctx->iv); + + while (block < finish) { + __m128i enc; + __m128i* keysched = (__m128i*)ctx->keysched;/* Key schedule ptr */ + + /* Perform rounds */ + enc = _mm_xor_si128(iv, *keysched); /* Note that we use IV */ + switch (ctx->Nr) { + case 14: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 12: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + case 10: + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenc_si128(enc, *(++keysched)); + enc = _mm_aesenclast_si128(enc, *(++keysched)); + break; + default: + assert(0); + } + + /* Xor with block and store result */ + enc = _mm_xor_si128(enc, _mm_loadu_si128(block)); + _mm_storeu_si128(block, enc); + + /* Increment of IV */ + iv = _mm_shuffle_epi8(iv, BSWAP_EPI64); /* Swap endianess */ + iv = _mm_add_epi64(iv, ONE); /* Inc low part */ + enc = _mm_cmpeq_epi64(iv, ZERO); /* Check for carry */ + enc = _mm_unpacklo_epi64(ZERO, enc); /* Pack carry reg */ + iv = _mm_sub_epi64(iv, enc); /* Sub carry reg */ + iv = _mm_shuffle_epi8(iv, BSWAP_EPI64); /* Swap enianess back */ + + /* Go to next block */ + ++block; + } + + /* Update IV */ + _mm_storeu_si128((__m128i*)ctx->iv, iv); +} + +FUNC_ISA +static void aes_inv_key_10(AESContext * ctx) +{ + __m128i* keysched = (__m128i*)ctx->keysched; + __m128i* invkeysched = (__m128i*)ctx->invkeysched; + + *(invkeysched + 10) = *(keysched + 0); + *(invkeysched + 9) = _mm_aesimc_si128(*(keysched + 1)); + *(invkeysched + 8) = _mm_aesimc_si128(*(keysched + 2)); + *(invkeysched + 7) = _mm_aesimc_si128(*(keysched + 3)); + *(invkeysched + 6) = _mm_aesimc_si128(*(keysched + 4)); + *(invkeysched + 5) = _mm_aesimc_si128(*(keysched + 5)); + *(invkeysched + 4) = _mm_aesimc_si128(*(keysched + 6)); + *(invkeysched + 3) = _mm_aesimc_si128(*(keysched + 7)); + *(invkeysched + 2) = _mm_aesimc_si128(*(keysched + 8)); + *(invkeysched + 1) = _mm_aesimc_si128(*(keysched + 9)); + *(invkeysched + 0) = *(keysched + 10); +} + +FUNC_ISA +static void aes_inv_key_12(AESContext * ctx) +{ + __m128i* keysched = (__m128i*)ctx->keysched; + __m128i* invkeysched = (__m128i*)ctx->invkeysched; + + *(invkeysched + 12) = *(keysched + 0); + *(invkeysched + 11) = _mm_aesimc_si128(*(keysched + 1)); + *(invkeysched + 10) = _mm_aesimc_si128(*(keysched + 2)); + *(invkeysched + 9) = _mm_aesimc_si128(*(keysched + 3)); + *(invkeysched + 8) = _mm_aesimc_si128(*(keysched + 4)); + *(invkeysched + 7) = _mm_aesimc_si128(*(keysched + 5)); + *(invkeysched + 6) = _mm_aesimc_si128(*(keysched + 6)); + *(invkeysched + 5) = _mm_aesimc_si128(*(keysched + 7)); + *(invkeysched + 4) = _mm_aesimc_si128(*(keysched + 8)); + *(invkeysched + 3) = _mm_aesimc_si128(*(keysched + 9)); + *(invkeysched + 2) = _mm_aesimc_si128(*(keysched + 10)); + *(invkeysched + 1) = _mm_aesimc_si128(*(keysched + 11)); + *(invkeysched + 0) = *(keysched + 12); +} + +FUNC_ISA +static void aes_inv_key_14(AESContext * ctx) +{ + __m128i* keysched = (__m128i*)ctx->keysched; + __m128i* invkeysched = (__m128i*)ctx->invkeysched; + + *(invkeysched + 14) = *(keysched + 0); + *(invkeysched + 13) = _mm_aesimc_si128(*(keysched + 1)); + *(invkeysched + 12) = _mm_aesimc_si128(*(keysched + 2)); + *(invkeysched + 11) = _mm_aesimc_si128(*(keysched + 3)); + *(invkeysched + 10) = _mm_aesimc_si128(*(keysched + 4)); + *(invkeysched + 9) = _mm_aesimc_si128(*(keysched + 5)); + *(invkeysched + 8) = _mm_aesimc_si128(*(keysched + 6)); + *(invkeysched + 7) = _mm_aesimc_si128(*(keysched + 7)); + *(invkeysched + 6) = _mm_aesimc_si128(*(keysched + 8)); + *(invkeysched + 5) = _mm_aesimc_si128(*(keysched + 9)); + *(invkeysched + 4) = _mm_aesimc_si128(*(keysched + 10)); + *(invkeysched + 3) = _mm_aesimc_si128(*(keysched + 11)); + *(invkeysched + 2) = _mm_aesimc_si128(*(keysched + 12)); + *(invkeysched + 1) = _mm_aesimc_si128(*(keysched + 13)); + *(invkeysched + 0) = *(keysched + 14); +} + +/* + * Set up an AESContext. `keylen' is measured in + * bytes; it can be either 16 (128-bit), 24 (192-bit), or 32 + * (256-bit). + */ +FUNC_ISA +static void aes_setup_ni(AESContext * ctx, + const unsigned char *key, int keylen) +{ + __m128i *keysched = (__m128i*)ctx->keysched; + + ctx->encrypt_cbc = aes_encrypt_cbc_ni; + ctx->decrypt_cbc = aes_decrypt_cbc_ni; + ctx->sdctr = aes_sdctr_ni; + + /* + * Now do the key setup itself. + */ + switch (keylen) { + case 16: + AES_128_Key_Expansion (key, keysched); + break; + case 24: + AES_192_Key_Expansion (key, keysched); + break; + case 32: + AES_256_Key_Expansion (key, keysched); + break; + default: + assert(0); + } + + /* + * Now prepare the modified keys for the inverse cipher. + */ + switch (ctx->Nr) { + case 10: + aes_inv_key_10(ctx); + break; + case 12: + aes_inv_key_12(ctx); + break; + case 14: + aes_inv_key_14(ctx); + break; + default: + assert(0); + } +} + +#else /* COMPILER_SUPPORTS_AES_NI */ + +static void aes_setup_ni(AESContext * ctx, const unsigned char *key, int keylen) +{ + assert(0); +} + +INLINE static bool supports_aes_ni() +{ + return false; +} + +#endif /* COMPILER_SUPPORTS_AES_NI */ diff --git a/ssharcf.c b/ssharcf.c index d16ce225..fe1282fa 100644 --- a/ssharcf.c +++ b/ssharcf.c @@ -9,10 +9,12 @@ typedef struct { unsigned char i, j, s[256]; + ssh2_cipher vt; } ArcfourContext; -static void arcfour_block(void *handle, unsigned char *blk, int len) +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; @@ -60,14 +62,18 @@ static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key, * to leak data about the key. */ -static void *arcfour_make_context(void) +static ssh2_cipher *arcfour_new(const struct ssh2_cipheralg *alg) { - return snew(ArcfourContext); + ArcfourContext *ctx = snew(ArcfourContext); + ctx->vt = alg; + return &ctx->vt; } -static void arcfour_free_context(void *handle) +static void arcfour_free(ssh2_cipher *cipher) { - sfree(handle); + ArcfourContext *ctx = container_of(cipher, ArcfourContext, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } static void arcfour_stir(ArcfourContext *ctx) @@ -79,42 +85,41 @@ static void arcfour_stir(ArcfourContext *ctx) sfree(junk); } -static void arcfour128_key(void *handle, unsigned char *key) +static void arcfour_ssh2_setiv(ssh2_cipher *cipher, const void *key) { - ArcfourContext *ctx = (ArcfourContext *)handle; - arcfour_setkey(ctx, key, 16); - arcfour_stir(ctx); + /* As a pure stream cipher, Arcfour has no IV separate from the key */ } -static void arcfour256_key(void *handle, unsigned char *key) +static void arcfour_ssh2_setkey(ssh2_cipher *cipher, const void *key) { - ArcfourContext *ctx = (ArcfourContext *)handle; - arcfour_setkey(ctx, key, 32); + ArcfourContext *ctx = container_of(cipher, ArcfourContext, vt); + arcfour_setkey(ctx, key, ctx->vt->padded_keybytes); arcfour_stir(ctx); } -static void arcfour_iv(void *handle, unsigned char *key) +static void arcfour_ssh2_block(ssh2_cipher *cipher, void *blk, int len) { - + ArcfourContext *ctx = container_of(cipher, ArcfourContext, vt); + arcfour_block(ctx, blk, len); } -const struct ssh2_cipher ssh_arcfour128_ssh2 = { - arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour128_key, - arcfour_block, arcfour_block, NULL, NULL, +const struct ssh2_cipheralg ssh_arcfour128_ssh2 = { + arcfour_new, arcfour_free, arcfour_ssh2_setiv, arcfour_ssh2_setkey, + arcfour_ssh2_block, arcfour_ssh2_block, NULL, NULL, "arcfour128", 1, 128, 16, 0, "Arcfour-128", NULL }; -const struct ssh2_cipher ssh_arcfour256_ssh2 = { - arcfour_make_context, arcfour_free_context, arcfour_iv, arcfour256_key, - arcfour_block, arcfour_block, NULL, NULL, +const struct ssh2_cipheralg ssh_arcfour256_ssh2 = { + arcfour_new, arcfour_free, arcfour_ssh2_setiv, arcfour_ssh2_setkey, + arcfour_ssh2_block, arcfour_ssh2_block, NULL, NULL, "arcfour256", 1, 256, 32, 0, "Arcfour-256", NULL }; -static const struct ssh2_cipher *const arcfour_list[] = { +static const struct ssh2_cipheralg *const arcfour_list[] = { &ssh_arcfour256_ssh2, &ssh_arcfour128_ssh2, }; diff --git a/sshbcrypt.c b/sshbcrypt.c index a6c163c6..c4584618 100644 --- a/sshbcrypt.c +++ b/sshbcrypt.c @@ -56,16 +56,13 @@ void bcrypt_genblock(int counter, { SHA512_State shastate; unsigned char hashed_salt[64]; - unsigned char countbuf[4]; /* Hash the input salt with the counter value optionally suffixed * to get our real 32-byte salt */ SHA512_Init(&shastate); - SHA512_Bytes(&shastate, salt, saltbytes); - if (counter) { - PUT_32BIT_MSB_FIRST(countbuf, counter); - SHA512_Bytes(&shastate, countbuf, 4); - } + put_data(&shastate, salt, saltbytes); + if (counter) + put_uint32(&shastate, counter); SHA512_Final(&shastate, hashed_salt); bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output); diff --git a/sshblowf.c b/sshblowf.c index 353116a6..c440556f 100644 --- a/sshblowf.c +++ b/sshblowf.c @@ -10,8 +10,8 @@ #include "sshblowf.h" struct BlowfishContext { - word32 S0[256], S1[256], S2[256], S3[256], P[18]; - word32 iv0, iv1; /* for CBC mode */ + uint32_t S0[256], S1[256], S2[256], S3[256], P[18]; + uint32_t iv0, iv1; /* for CBC mode */ }; /* @@ -27,7 +27,7 @@ struct BlowfishContext { 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 word32 ${name}[] = {\n"; + print "static const uint32_t ${name}[] = {\n"; my $len = $name eq "parray" ? 18 : 256; for my $i (1..$len) { read $spig, $word, 8; @@ -39,13 +39,13 @@ for my $name ("parray", "sbox0".."sbox3") { close $spig; */ -static const word32 parray[] = { +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 word32 sbox0[] = { +static const uint32_t sbox0[] = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, @@ -91,7 +91,7 @@ static const word32 sbox0[] = { 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A, }; -static const word32 sbox1[] = { +static const uint32_t sbox1[] = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, @@ -137,7 +137,7 @@ static const word32 sbox1[] = { 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7, }; -static const word32 sbox2[] = { +static const uint32_t sbox2[] = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, @@ -183,7 +183,7 @@ static const word32 sbox2[] = { 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0, }; -static const word32 sbox3[] = { +static const uint32_t sbox3[] = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, @@ -233,15 +233,15 @@ static const word32 sbox3[] = { #define F(x) Fprime( ((x>>24)&0xFF), ((x>>16)&0xFF), ((x>>8)&0xFF), (x&0xFF) ) #define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t ) -static void blowfish_encrypt(word32 xL, word32 xR, word32 * output, +static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output, BlowfishContext * ctx) { - word32 *S0 = ctx->S0; - word32 *S1 = ctx->S1; - word32 *S2 = ctx->S2; - word32 *S3 = ctx->S3; - word32 *P = ctx->P; - word32 t; + 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); @@ -266,15 +266,15 @@ static void blowfish_encrypt(word32 xL, word32 xR, word32 * output, output[1] = xL; } -static void blowfish_decrypt(word32 xL, word32 xR, word32 * output, +static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output, BlowfishContext * ctx) { - word32 *S0 = ctx->S0; - word32 *S1 = ctx->S1; - word32 *S2 = ctx->S2; - word32 *S3 = ctx->S3; - word32 *P = ctx->P; - word32 t; + 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); @@ -300,9 +300,9 @@ static void blowfish_decrypt(word32 xL, word32 xR, word32 * output, } static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, - BlowfishContext * ctx) + BlowfishContext * ctx) { - word32 xL, xR, out[2], iv0, iv1; + uint32_t xL, xR, out[2], iv0, iv1; assert((len & 7) == 0); @@ -327,10 +327,10 @@ static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len, ctx->iv1 = iv1; } -void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len, - BlowfishContext * ctx) +void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext * ctx) { - word32 xL, xR, out[2]; + unsigned char *blk = (unsigned char *)vblk; + uint32_t xL, xR, out[2]; assert((len & 7) == 0); @@ -348,7 +348,7 @@ void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len, static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, BlowfishContext * ctx) { - word32 xL, xR, out[2], iv0, iv1; + uint32_t xL, xR, out[2], iv0, iv1; assert((len & 7) == 0); @@ -376,7 +376,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) { - word32 xL, xR, out[2], iv0, iv1; + uint32_t xL, xR, out[2], iv0, iv1; assert((len & 7) == 0); @@ -404,7 +404,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) { - word32 xL, xR, out[2], iv0, iv1; + uint32_t xL, xR, out[2], iv0, iv1; assert((len & 7) == 0); @@ -432,7 +432,7 @@ static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len, static void blowfish_msb_sdctr(unsigned char *blk, int len, BlowfishContext * ctx) { - word32 b[2], iv0, iv1, tmp; + uint32_t b[2], iv0, iv1, tmp; assert((len & 7) == 0); @@ -472,15 +472,17 @@ void blowfish_initkey(BlowfishContext *ctx) } void blowfish_expandkey(BlowfishContext * ctx, - const unsigned char *key, short keybytes, - const unsigned char *salt, short saltbytes) + const void *vkey, short keybytes, + const void *vsalt, short saltbytes) { - word32 *S0 = ctx->S0; - word32 *S1 = ctx->S1; - word32 *S2 = ctx->S2; - word32 *S3 = ctx->S3; - word32 *P = ctx->P; - word32 str[2]; + 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]; @@ -494,19 +496,19 @@ void blowfish_expandkey(BlowfishContext * ctx, for (i = 0; i < 18; i++) { P[i] ^= - ((word32) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; + ((uint32_t) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; P[i] ^= - ((word32) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; + ((uint32_t) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; P[i] ^= - ((word32) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8; - P[i] ^= ((word32) (unsigned char) (key[(i * 4 + 3) % keybytes])); + ((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] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); blowfish_encrypt(str[0], str[1], str, ctx); P[i] = str[0]; @@ -515,28 +517,28 @@ void blowfish_expandkey(BlowfishContext * ctx, for (i = 0; i < 256; i += 2) { for (j = 0; j < 8; j++) - str[j/4] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + 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] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + 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] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + 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] ^= ((word32)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); + 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]; @@ -552,110 +554,150 @@ static void blowfish_setkey(BlowfishContext *ctx, /* -- Interface with PuTTY -- */ -#define SSH_SESSION_KEY_LENGTH 32 +#define SSH1_SESSION_KEY_LENGTH 32 -void *blowfish_make_context(void) +BlowfishContext *blowfish_make_context(void) { return snew(BlowfishContext); } -static void *blowfish_ssh1_make_context(void) +void blowfish_free_context(BlowfishContext *ctx) +{ + sfree(ctx); +} + +static void blowfish_iv(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); +} + +struct blowfish_ssh1_ctx { /* In SSH-1, need one key for each direction */ - return snewn(2, BlowfishContext); + BlowfishContext contexts[2]; + ssh1_cipher vt; +}; + +static ssh1_cipher *blowfish_ssh1_new(void) +{ + struct blowfish_ssh1_ctx *ctx = snew(struct blowfish_ssh1_ctx); + ctx->vt = &ssh1_blowfish; + return &ctx->vt; } -void blowfish_free_context(void *handle) +static void blowfish_ssh1_free(ssh1_cipher *cipher) { - sfree(handle); + struct blowfish_ssh1_ctx *ctx = + container_of(cipher, struct blowfish_ssh1_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void blowfish_key(void *handle, unsigned char *key) +static void blowfish_ssh1_sesskey(ssh1_cipher *cipher, const void *key) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_setkey(ctx, key, 16); + struct blowfish_ssh1_ctx *ctx = + container_of(cipher, struct blowfish_ssh1_ctx, vt); + blowfish_setkey(&ctx->contexts[0], key, SSH1_SESSION_KEY_LENGTH); + ctx->contexts[0].iv0 = ctx->contexts[0].iv1 = 0; + ctx->contexts[1] = ctx->contexts[0]; /* structure copy */ } -static void blowfish256_key(void *handle, unsigned char *key) +static void blowfish_ssh1_encrypt_blk(ssh1_cipher *cipher, void *blk, int len) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_setkey(ctx, key, 32); + struct blowfish_ssh1_ctx *ctx = + container_of(cipher, struct blowfish_ssh1_ctx, vt); + blowfish_lsb_encrypt_cbc(blk, len, ctx->contexts); } -static void blowfish_iv(void *handle, unsigned char *key) +static void blowfish_ssh1_decrypt_blk(ssh1_cipher *cipher, void *blk, int len) +{ + struct blowfish_ssh1_ctx *ctx = + container_of(cipher, struct blowfish_ssh1_ctx, vt); + blowfish_lsb_decrypt_cbc(blk, len, ctx->contexts+1); +} + +struct blowfish_ssh2_ctx { + BlowfishContext context; + ssh2_cipher vt; +}; + +static ssh2_cipher *blowfish_ssh2_new(const struct ssh2_cipheralg *alg) { - BlowfishContext *ctx = (BlowfishContext *)handle; - ctx->iv0 = GET_32BIT_MSB_FIRST(key); - ctx->iv1 = GET_32BIT_MSB_FIRST(key + 4); + struct blowfish_ssh2_ctx *ctx = snew(struct blowfish_ssh2_ctx); + ctx->vt = alg; + return &ctx->vt; } -static void blowfish_sesskey(void *handle, unsigned char *key) +static void blowfish_ssh2_free(ssh2_cipher *cipher) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_setkey(ctx, key, SSH_SESSION_KEY_LENGTH); - ctx->iv0 = 0; - ctx->iv1 = 0; - ctx[1] = ctx[0]; /* structure copy */ + struct blowfish_ssh2_ctx *ctx = + container_of(cipher, struct blowfish_ssh2_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void blowfish_ssh1_encrypt_blk(void *handle, unsigned char *blk, - int len) +static void blowfish_ssh2_setiv(ssh2_cipher *cipher, const void *iv) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_lsb_encrypt_cbc(blk, len, ctx); + struct blowfish_ssh2_ctx *ctx = + container_of(cipher, struct blowfish_ssh2_ctx, vt); + blowfish_iv(&ctx->context, iv); } -static void blowfish_ssh1_decrypt_blk(void *handle, unsigned char *blk, - int len) +static void blowfish_ssh2_setkey(ssh2_cipher *cipher, const void *key) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_lsb_decrypt_cbc(blk, len, ctx+1); + struct blowfish_ssh2_ctx *ctx = + container_of(cipher, struct blowfish_ssh2_ctx, vt); + blowfish_setkey(&ctx->context, key, ctx->vt->padded_keybytes); } -static void blowfish_ssh2_encrypt_blk(void *handle, unsigned char *blk, - int len) +static void blowfish_ssh2_encrypt_blk(ssh2_cipher *cipher, void *blk, int len) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_msb_encrypt_cbc(blk, len, ctx); + struct blowfish_ssh2_ctx *ctx = + container_of(cipher, struct blowfish_ssh2_ctx, vt); + blowfish_msb_encrypt_cbc(blk, len, &ctx->context); } -static void blowfish_ssh2_decrypt_blk(void *handle, unsigned char *blk, - int len) +static void blowfish_ssh2_decrypt_blk(ssh2_cipher *cipher, void *blk, int len) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_msb_decrypt_cbc(blk, len, ctx); + struct blowfish_ssh2_ctx *ctx = + container_of(cipher, struct blowfish_ssh2_ctx, vt); + blowfish_msb_decrypt_cbc(blk, len, &ctx->context); } -static void blowfish_ssh2_sdctr(void *handle, unsigned char *blk, - int len) +static void blowfish_ssh2_sdctr(ssh2_cipher *cipher, void *blk, int len) { - BlowfishContext *ctx = (BlowfishContext *)handle; - blowfish_msb_sdctr(blk, len, ctx); + struct blowfish_ssh2_ctx *ctx = + container_of(cipher, struct blowfish_ssh2_ctx, vt); + blowfish_msb_sdctr(blk, len, &ctx->context); } -const struct ssh_cipher ssh_blowfish_ssh1 = { - blowfish_ssh1_make_context, blowfish_free_context, blowfish_sesskey, +const struct ssh1_cipheralg ssh1_blowfish = { + blowfish_ssh1_new, blowfish_ssh1_free, + blowfish_ssh1_sesskey, blowfish_ssh1_encrypt_blk, blowfish_ssh1_decrypt_blk, 8, "Blowfish-128 CBC" }; -static const struct ssh2_cipher ssh_blowfish_ssh2 = { - blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish_key, +static const struct ssh2_cipheralg ssh_blowfish_ssh2 = { + blowfish_ssh2_new, blowfish_ssh2_free, + blowfish_ssh2_setiv, blowfish_ssh2_setkey, blowfish_ssh2_encrypt_blk, blowfish_ssh2_decrypt_blk, NULL, NULL, "blowfish-cbc", 8, 128, 16, SSH_CIPHER_IS_CBC, "Blowfish-128 CBC", NULL }; -static const struct ssh2_cipher ssh_blowfish_ssh2_ctr = { - blowfish_make_context, blowfish_free_context, blowfish_iv, blowfish256_key, +static const struct ssh2_cipheralg ssh_blowfish_ssh2_ctr = { + blowfish_ssh2_new, blowfish_ssh2_free, + blowfish_ssh2_setiv, blowfish_ssh2_setkey, blowfish_ssh2_sdctr, blowfish_ssh2_sdctr, NULL, NULL, "blowfish-ctr", 8, 256, 32, 0, "Blowfish-256 SDCTR", NULL }; -static const struct ssh2_cipher *const blowfish_list[] = { +static const struct ssh2_cipheralg *const blowfish_list[] = { &ssh_blowfish_ssh2_ctr, &ssh_blowfish_ssh2 }; diff --git a/sshblowf.h b/sshblowf.h index eb6a48c1..a9efe3da 100644 --- a/sshblowf.h +++ b/sshblowf.h @@ -5,11 +5,10 @@ typedef struct BlowfishContext BlowfishContext; -void *blowfish_make_context(void); -void blowfish_free_context(void *handle); +BlowfishContext *blowfish_make_context(void); +void blowfish_free_context(BlowfishContext *ctx); void blowfish_initkey(BlowfishContext *ctx); void blowfish_expandkey(BlowfishContext *ctx, - const unsigned char *key, short keybytes, - const unsigned char *salt, short saltbytes); -void blowfish_lsb_encrypt_ecb(unsigned char *blk, int len, - 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/sshbn.c b/sshbn.c index 6768204b..d61e3ab6 100644 --- a/sshbn.c +++ b/sshbn.c @@ -17,6 +17,7 @@ typedef BignumInt *Bignum; #include "ssh.h" +#include "marshal.h" BignumInt bnZero[1] = { 0 }; BignumInt bnOne[2] = { 1, 1 }; @@ -1403,8 +1404,9 @@ void decbn(Bignum bn) bn[i]--; } -Bignum bignum_from_bytes(const unsigned char *data, int nbytes) +Bignum bignum_from_bytes(const void *vdata, int nbytes) { + const unsigned char *data = (const unsigned char *)vdata; Bignum result; int w, i; @@ -1425,8 +1427,9 @@ Bignum bignum_from_bytes(const unsigned char *data, int nbytes) return result; } -Bignum bignum_from_bytes_le(const unsigned char *data, int nbytes) +Bignum bignum_from_bytes_le(const void *vdata, int nbytes) { + const unsigned char *data = (const unsigned char *)vdata; Bignum result; int w, i; @@ -1506,36 +1509,7 @@ Bignum bignum_random_in_range(const Bignum lower, const Bignum upper) } /* - * Read an SSH-1-format bignum from a data buffer. Return the number - * of bytes consumed, or -1 if there wasn't enough data. - */ -int ssh1_read_bignum(const unsigned char *data, int len, Bignum * result) -{ - const unsigned char *p = data; - int i; - int w, b; - - if (len < 2) - return -1; - - w = 0; - for (i = 0; i < 2; i++) - w = (w << 8) + *p++; - b = (w + 7) / 8; /* bits -> bytes */ - - if (len < b+2) - return -1; - - if (!result) /* just return length */ - return b + 2; - - *result = bignum_from_bytes(p, b); - - return p + b - data; -} - -/* - * Return the bit count of a bignum, for SSH-1 encoding. + * Return the bit count of a bignum. */ int bignum_bitcount(Bignum bn) { @@ -1545,22 +1519,6 @@ int bignum_bitcount(Bignum bn) return bitcount + 1; } -/* - * Return the byte length of a bignum when SSH-1 encoded. - */ -int ssh1_bignum_length(Bignum bn) -{ - return 2 + (bignum_bitcount(bn) + 7) / 8; -} - -/* - * Return the byte length of a bignum when SSH-2 encoded. - */ -int ssh2_bignum_length(Bignum bn) -{ - return 4 + (bignum_bitcount(bn) + 8) / 8; -} - /* * Return a byte from a bignum; 0 is least significant, etc. */ @@ -1601,22 +1559,61 @@ void bignum_set_bit(Bignum bn, int bitnum, int value) } } -/* - * Write a SSH-1-format bignum into a buffer. It is assumed the - * buffer is big enough. Returns the number of bytes used. - */ -int ssh1_write_bignum(void *data, Bignum bn) +void BinarySink_put_mp_ssh1(BinarySink *bs, Bignum bn) { - unsigned char *p = data; - int len = ssh1_bignum_length(bn); + int bits = bignum_bitcount(bn); + int bytes = (bits + 7) / 8; int i; - int bitc = bignum_bitcount(bn); - *p++ = (bitc >> 8) & 0xFF; - *p++ = (bitc) & 0xFF; - for (i = len - 2; i--;) - *p++ = bignum_byte(bn, i); - return len; + put_uint16(bs, bits); + for (i = bytes; i--;) + put_byte(bs, bignum_byte(bn, i)); +} + +void BinarySink_put_mp_ssh2(BinarySink *bs, Bignum bn) +{ + int bytes = (bignum_bitcount(bn) + 8) / 8; + int i; + + put_uint32(bs, bytes); + for (i = bytes; i--;) + put_byte(bs, bignum_byte(bn, i)); +} + +Bignum BinarySource_get_mp_ssh1(BinarySource *src) +{ + unsigned bitc = get_uint16(src); + ptrlen bytes = get_data(src, (bitc + 7) / 8); + if (get_err(src)) { + return bignum_from_long(0); + } else { + Bignum toret = bignum_from_bytes(bytes.ptr, bytes.len); + /* SSH-1.5 spec says that it's OK for the prefix uint16 to be + * _greater_ than the actual number of bits */ + if (bignum_bitcount(toret) > bitc) { + src->err = BSE_INVALID; + freebn(toret); + toret = bignum_from_long(0); + } + return toret; + } +} + +Bignum BinarySource_get_mp_ssh2(BinarySource *src) +{ + ptrlen bytes = get_string(src); + if (get_err(src)) { + return bignum_from_long(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 bignum_from_long(0); + } + return bignum_from_bytes(bytes.ptr, bytes.len); + } } /* @@ -2088,7 +2085,8 @@ Bignum modinv(Bignum number, Bignum modulus) char *bignum_decimal(Bignum x) { int ndigits, ndigit; - int i, iszero; + int i; + bool iszero; BignumInt carry; char *ret; BignumInt *workspace; @@ -2133,7 +2131,7 @@ char *bignum_decimal(Bignum x) ndigit = ndigits - 1; ret[ndigit] = '\0'; do { - iszero = 1; + iszero = true; carry = 0; for (i = 0; i < (int)x[0]; i++) { /* @@ -2162,7 +2160,7 @@ char *bignum_decimal(Bignum x) carry = r; if (workspace[i]) - iszero = 0; + iszero = false; } ret[--ndigit] = (char) (carry + '0'); } while (!iszero); diff --git a/sshbpp.h b/sshbpp.h new file mode 100644 index 00000000..26ff477d --- /dev/null +++ b/sshbpp.h @@ -0,0 +1,149 @@ +/* + * Abstraction of the binary packet protocols used in SSH. + */ + +#ifndef PUTTY_SSHBPP_H +#define PUTTY_SSHBPP_H + +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); +}; + +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; + + int remote_bugs; + + /* 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; +}; + +#define ssh_bpp_handle_input(bpp) ((bpp)->vt->handle_input(bpp)) +#define ssh_bpp_handle_output(bpp) ((bpp)->vt->handle_output(bpp)) +#define ssh_bpp_new_pktout(bpp, type) ((bpp)->vt->new_pktout(type)) +#define ssh_bpp_queue_disconnect(bpp, msg, cat) \ + ((bpp)->vt->queue_disconnect(bpp, msg, cat)) + +/* 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 struct ssh1_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, and + * takes a double pair of parens because it passes a whole argument + * list to dupprintf. */ +#define bpp_logevent(params) ( \ + logevent_and_free((bpp)->logctx, dupprintf params)) + +/* + * 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 macro DTS_CONSUME subtracts a given amount + * from the counter in a particular direction, and evaluates to a + * boolean indicating whether 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 DataTransferStats { + struct { + bool running; + unsigned long remaining; + } in, out; +}; +#define DTS_CONSUME(stats, direction, size) \ + ((stats)->direction.running && \ + (stats)->direction.remaining <= (size) ? \ + ((stats)->direction.running = false, true) : \ + ((stats)->direction.remaining -= (size), false)) + +BinaryPacketProtocol *ssh2_bpp_new( + LogContext *logctx, struct DataTransferStats *stats, bool is_server); +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipheralg *cipher, const void *ckey, const void *iv, + const struct ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const struct ssh_compression_alg *compression, bool delayed_compression); +void ssh2_bpp_new_incoming_crypto( + BinaryPacketProtocol *bpp, + const struct ssh2_cipheralg *cipher, const void *ckey, const void *iv, + const struct ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const struct 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/sshccp.c b/sshccp.c index 835b8fe5..1ce40699 100644 --- a/sshccp.c +++ b/sshccp.c @@ -20,7 +20,7 @@ * 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 ssh2_cipher structure ensures that the MAC is + * configuration of the ssh2_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 @@ -45,7 +45,7 @@ struct chacha20 { * 4-11 are the key * 12-13 are the counter * 14-15 are the IV */ - uint32 state[16]; + 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 */ @@ -55,7 +55,7 @@ struct chacha20 { static INLINE void chacha20_round(struct chacha20 *ctx) { int i; - uint32 copy[16]; + uint32_t copy[16]; /* Take a copy */ memcpy(copy, ctx->state, sizeof(copy)); @@ -114,7 +114,7 @@ static INLINE void chacha20_round(struct chacha20 *ctx) /* 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)(ctx->state[12])) { + if (!(uint32_t)(ctx->state[12])) { ++ctx->state[13]; } } @@ -864,35 +864,44 @@ struct ccp_context { unsigned char mac_iv[8]; struct poly1305 mac; + + BinarySink_IMPLEMENTATION; + ssh2_cipher cvt; + ssh2_mac mac_if; }; -static void *poly_make_context(void *ctx) +static ssh2_mac *poly_ssh2_new( + const struct ssh2_macalg *alg, ssh2_cipher *cipher) { - return ctx; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); + ctx->mac_if.vt = alg; + BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); + return &ctx->mac_if; } -static void poly_free_context(void *ctx) +static void poly_ssh2_free(ssh2_mac *mac) { /* Not allocated, just forwarded, no need to free */ } -static void poly_setkey(void *ctx, unsigned char *key) +static void poly_setkey(ssh2_mac *mac, const void *key) { /* Uses the same context as ChaCha20, so ignore */ } -static void poly_start(void *handle) +static void poly_start(ssh2_mac *mac) { - struct ccp_context *ctx = (struct ccp_context *)handle; + 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_bytes(void *handle, unsigned char const *blk, int len) +static void poly_BinarySink_write(BinarySink *bs, const void *blkv, size_t len) { - struct ccp_context *ctx = (struct ccp_context *)handle; + 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) { @@ -922,106 +931,68 @@ static void poly_bytes(void *handle, unsigned char const *blk, int len) } } -static void poly_genresult(void *handle, unsigned char *blk) +static void poly_genresult(ssh2_mac *mac, unsigned char *blk) { - struct ccp_context *ctx = (struct ccp_context *)handle; + struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); poly1305_finalise(&ctx->mac, blk); } -static int poly_verresult(void *handle, unsigned char const *blk) -{ - struct ccp_context *ctx = (struct ccp_context *)handle; - int res; - unsigned char mac[16]; - poly1305_finalise(&ctx->mac, mac); - res = smemeq(blk, mac, 16); - return res; -} - -/* The generic poly operation used before generate and verify */ -static void poly_op(void *handle, unsigned char *blk, int len, unsigned long seq) -{ - unsigned char iv[4]; - poly_start(handle); - PUT_32BIT_MSB_FIRST(iv, seq); - /* poly_bytes expects the first 4 bytes to be the IV */ - poly_bytes(handle, iv, 4); - smemclr(iv, sizeof(iv)); - poly_bytes(handle, blk, len); -} - -static void poly_generate(void *handle, unsigned char *blk, int len, unsigned long seq) -{ - poly_op(handle, blk, len, seq); - poly_genresult(handle, blk+len); -} - -static int poly_verify(void *handle, unsigned char *blk, int len, unsigned long seq) -{ - poly_op(handle, blk, len, seq); - return poly_verresult(handle, blk+len); -} - -static const struct ssh_mac ssh2_poly1305 = { - poly_make_context, poly_free_context, - poly_setkey, - - /* whole-packet operations */ - poly_generate, poly_verify, - - /* partial-packet operations */ - poly_start, poly_bytes, poly_genresult, poly_verresult, +static const struct ssh2_macalg ssh2_poly1305 = { + poly_ssh2_new, poly_ssh2_free, poly_setkey, + poly_start, poly_genresult, "", "", /* Not selectable individually, just part of ChaCha20-Poly1305 */ 16, 0, "Poly1305" }; -static void *ccp_make_context(void) +static ssh2_cipher *ccp_new(const struct ssh2_cipheralg *alg) { struct ccp_context *ctx = snew(struct ccp_context); - if (ctx) { - poly1305_init(&ctx->mac); - } - return ctx; + BinarySink_INIT(ctx, poly_BinarySink_write); + poly1305_init(&ctx->mac); + ctx->cvt = alg; + return &ctx->cvt; } -static void ccp_free_context(void *vctx) +static void ccp_free(ssh2_cipher *cipher) { - struct ccp_context *ctx = (struct ccp_context *)vctx; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); 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(void *vctx, unsigned char *iv) +static void ccp_iv(ssh2_cipher *cipher, const void *iv) { - /* struct ccp_context *ctx = (struct ccp_context *)vctx; */ + /* struct ccp_context *ctx = + container_of(cipher, struct ccp_context, cvt); */ /* IV is set based on the sequence number */ } -static void ccp_key(void *vctx, unsigned char *key) +static void ccp_key(ssh2_cipher *cipher, const void *vkey) { - struct ccp_context *ctx = (struct ccp_context *)vctx; + const unsigned char *key = (const unsigned char *)vkey; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); /* 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(void *vctx, unsigned char *blk, int len) +static void ccp_encrypt(ssh2_cipher *cipher, void *blk, int len) { - struct ccp_context *ctx = (struct ccp_context *)vctx; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); chacha20_encrypt(&ctx->b_cipher, blk, len); } -static void ccp_decrypt(void *vctx, unsigned char *blk, int len) +static void ccp_decrypt(ssh2_cipher *cipher, void *blk, int len) { - struct ccp_context *ctx = (struct ccp_context *)vctx; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); chacha20_decrypt(&ctx->b_cipher, blk, len); } -static void ccp_length_op(struct ccp_context *ctx, unsigned char *blk, int len, +static void ccp_length_op(struct ccp_context *ctx, void *blk, int len, unsigned long seq) { unsigned char iv[8]; @@ -1038,26 +1009,26 @@ static void ccp_length_op(struct ccp_context *ctx, unsigned char *blk, int len, smemclr(iv, sizeof(iv)); } -static void ccp_encrypt_length(void *vctx, unsigned char *blk, int len, +static void ccp_encrypt_length(ssh2_cipher *cipher, void *blk, int len, unsigned long seq) { - struct ccp_context *ctx = (struct ccp_context *)vctx; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); ccp_length_op(ctx, blk, len, seq); chacha20_encrypt(&ctx->a_cipher, blk, len); } -static void ccp_decrypt_length(void *vctx, unsigned char *blk, int len, +static void ccp_decrypt_length(ssh2_cipher *cipher, void *blk, int len, unsigned long seq) { - struct ccp_context *ctx = (struct ccp_context *)vctx; + struct ccp_context *ctx = container_of(cipher, struct ccp_context, cvt); ccp_length_op(ctx, blk, len, seq); chacha20_decrypt(&ctx->a_cipher, blk, len); } -static const struct ssh2_cipher ssh2_chacha20_poly1305 = { +static const struct ssh2_cipheralg ssh2_chacha20_poly1305 = { - ccp_make_context, - ccp_free_context, + ccp_new, + ccp_free, ccp_iv, ccp_key, ccp_encrypt, @@ -1071,7 +1042,7 @@ static const struct ssh2_cipher ssh2_chacha20_poly1305 = { &ssh2_poly1305 }; -static const struct ssh2_cipher *const ccp_list[] = { +static const struct ssh2_cipheralg *const ccp_list[] = { &ssh2_chacha20_poly1305 }; diff --git a/sshchan.h b/sshchan.h new file mode 100644 index 00000000..9ec256d6 --- /dev/null +++ b/sshchan.h @@ -0,0 +1,274 @@ +/* + * Abstraction of the various ways to handle the local end of an SSH + * connection-layer channel. + */ + +#ifndef PUTTY_SSHCHAN_H +#define PUTTY_SSHCHAN_H + +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); + + int (*send)(Channel *, bool is_stderr, const void *buf, int 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; +}; + +#define chan_free(ch) ((ch)->vt->free(ch)) +#define chan_open_confirmation(ch) ((ch)->vt->open_confirmation(ch)) +#define chan_open_failed(ch, err) ((ch)->vt->open_failed(ch, err)) +#define chan_send(ch, err, buf, len) ((ch)->vt->send(ch, err, buf, len)) +#define chan_send_eof(ch) ((ch)->vt->send_eof(ch)) +#define chan_set_input_wanted(ch, wanted) \ + ((ch)->vt->set_input_wanted(ch, wanted)) +#define chan_log_close_msg(ch) ((ch)->vt->log_close_msg(ch)) +#define chan_want_close(ch, leof, reof) ((ch)->vt->want_close(ch, leof, reof)) +#define chan_rcvd_exit_status(ch, status) \ + ((ch)->vt->rcvd_exit_status(ch, status)) +#define chan_rcvd_exit_signal(ch, sig, core, msg) \ + ((ch)->vt->rcvd_exit_signal(ch, sig, core, msg)) +#define chan_rcvd_exit_signal_numeric(ch, sig, core, msg) \ + ((ch)->vt->rcvd_exit_signal_numeric(ch, sig, core, msg)) +#define chan_run_shell(ch) \ + ((ch)->vt->run_shell(ch)) +#define chan_run_command(ch, cmd) \ + ((ch)->vt->run_command(ch, cmd)) +#define chan_run_subsystem(ch, subsys) \ + ((ch)->vt->run_subsystem(ch, subsys)) +#define chan_enable_x11_forwarding(ch, oneshot, ap, ad, scr) \ + ((ch)->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr)) +#define chan_enable_agent_forwarding(ch) \ + ((ch)->vt->enable_agent_forwarding(ch)) +#define chan_allocate_pty(ch, termtype, w, h, pw, ph, modes) \ + ((ch)->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes)) +#define chan_set_env(ch, var, value) \ + ((ch)->vt->set_env(ch, var, value)) +#define chan_send_break(ch, length) \ + ((ch)->vt->send_break(ch, length)) +#define chan_send_signal(ch, signame) \ + ((ch)->vt->send_signal(ch, signame)) +#define chan_change_window_size(ch, w, h, pw, ph) \ + ((ch)->vt->change_window_size(ch, w, h, pw, ph)) +#define chan_request_response(ch, 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. + */ + +struct SshChannelVtable { + int (*write)(SshChannel *c, bool is_stderr, const void *, int); + void (*write_eof)(SshChannel *c); + void (*initiate_close)(SshChannel *c, const char *err); + void (*unthrottle)(SshChannel *c, int 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; +}; + +#define sshfwd_write(c, buf, len) ((c)->vt->write(c, false, buf, len)) +#define sshfwd_write_ext(c, stderr, buf, len) \ + ((c)->vt->write(c, stderr, buf, len)) +#define sshfwd_write_eof(c) ((c)->vt->write_eof(c)) +#define sshfwd_initiate_close(c, err) ((c)->vt->initiate_close(c, err)) +#define sshfwd_unthrottle(c, bufsize) ((c)->vt->unthrottle(c, bufsize)) +#define sshfwd_get_conf(c) ((c)->vt->get_conf(c)) +#define sshfwd_window_override_removed(c) ((c)->vt->window_override_removed(c)) +#define sshfwd_x11_sharing_handover(c, cs, ch, pa, pp, e, pmaj, pmin, d, l) \ + ((c)->vt->x11_sharing_handover(c, cs, ch, pa, pp, e, pmaj, pmin, d, l)) +#define sshfwd_send_exit_status(c, status) \ + ((c)->vt->send_exit_status(c, status)) +#define sshfwd_send_exit_signal(c, sig, core, msg) \ + ((c)->vt->send_exit_signal(c, sig, core, msg)) +#define sshfwd_send_exit_signal_numeric(c, sig, core, msg) \ + ((c)->vt->send_exit_signal_numeric(c, sig, core, msg)) +#define sshfwd_request_x11_forwarding(c, wr, ap, ad, scr, oneshot) \ + ((c)->vt->request_x11_forwarding(c, wr, ap, ad, scr, oneshot)) +#define sshfwd_request_agent_forwarding(c, wr) \ + ((c)->vt->request_agent_forwarding(c, wr)) +#define sshfwd_request_pty(c, wr, conf, w, h) \ + ((c)->vt->request_pty(c, wr, conf, w, h)) +#define sshfwd_send_env_var(c, wr, var, value) \ + ((c)->vt->send_env_var(c, wr, var, value)) +#define sshfwd_start_shell(c, wr) \ + ((c)->vt->start_shell(c, wr)) +#define sshfwd_start_command(c, wr, cmd) \ + ((c)->vt->start_command(c, wr, cmd)) +#define sshfwd_start_subsystem(c, wr, subsys) \ + ((c)->vt->start_subsystem(c, wr, subsys)) +#define sshfwd_send_serial_break(c, wr, length) \ + ((c)->vt->send_serial_break(c, wr, length)) +#define sshfwd_send_signal(c, wr, sig) \ + ((c)->vt->send_signal(c, wr, sig)) +#define sshfwd_send_terminal_size_change(c, w, h) \ + ((c)->vt->send_terminal_size_change(c, w, h)) +#define sshfwd_hint_channel_is_simple(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 new file mode 100644 index 00000000..563e4130 --- /dev/null +++ b/sshcommon.c @@ -0,0 +1,1073 @@ +/* + * Supporting routines used in common by all the various components of + * the SSH system. + */ + +#include +#include + +#include "putty.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; + + 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; + + 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 PktIn *pq_in_after(PacketQueueBase *pqb, + PacketQueueNode *prev, bool pop) +{ + PacketQueueNode *node = prev->next; + if (node == &pqb->end) + return NULL; + + if (pop) { + node->next->prev = node->prev; + node->prev->next = node->next; + + 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) { + node->next->prev = node->prev; + node->prev->next = node->next; + 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; +} + +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; +} + +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; + + /* + * 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; + + /* + * 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); + } +} + +/* ---------------------------------------------------------------------- + * 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_ensure(PktOut *pkt, int length) +{ + if (pkt->maxlen < length) { + pkt->maxlen = length + 256; + pkt->data = sresize(pkt->data, pkt->maxlen, unsigned char); + } +} +static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len) +{ + pkt->length += len; + ssh_pkt_ensure(pkt, pkt->length); + memcpy(pkt->data + pkt->length - len, data, len); +} + +static void ssh_pkt_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 int zombiechan_send(Channel *chan, bool is_stderr, const void *, int); +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 struct ChannelVtable zombiechan_channelvt = { + zombiechan_free, + zombiechan_do_nothing, /* open_confirmation */ + zombiechan_open_failure, + zombiechan_send, + zombiechan_do_nothing, /* send_eof */ + zombiechan_set_input_wanted, + zombiechan_log_close_msg, + zombiechan_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + 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 int zombiechan_send(Channel *chan, bool is_stderr, + const void *data, int 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; +} + +/* ---------------------------------------------------------------------- + * Centralised standard methods for other channel implementations to + * borrow. + */ + +void chan_remotely_opened_confirmation(Channel *chan) +{ + assert(0 && "this channel type should never receive OPEN_CONFIRMATION"); +} + +void chan_remotely_opened_failure(Channel *chan, const char *errtext) +{ + assert(0 && "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) +{ + assert(0 && "this channel type should never send a want-reply request"); +} + +/* ---------------------------------------------------------------------- + * 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: + assert(0 && "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. + */ + +bool first_in_commasep_string(char const *needle, char const *haystack, + int haylen) +{ + int needlen; + if (!needle || !haystack) /* protect against null pointers */ + return false; + needlen = strlen(needle); + + if (haylen >= needlen && /* haystack is long enough */ + !memcmp(needle, haystack, needlen) && /* initial match */ + (haylen == needlen || haystack[needlen] == ',') + /* either , or EOS follows */ + ) + return true; + return false; +} + +bool in_commasep_string(char const *needle, char const *haystack, int haylen) +{ + char *p; + + if (!needle || !haystack) /* protect against null pointers */ + return false; + /* + * Is it at the start of the string? + */ + if (first_in_commasep_string(needle, haystack, haylen)) + return true; + /* + * If not, search for the next comma and resume after that. + * If no comma found, terminate. + */ + p = memchr(haystack, ',', haylen); + if (!p) + return false; + /* + 1 to skip over comma */ + return in_commasep_string(needle, p + 1, haylen - (p + 1 - haystack)); +} + +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(ppl->seat, text, strlen(text)); + sfree(text); +} + +/* ---------------------------------------------------------------------- + * Common helper functions for clients and implementations of + * BinaryPacketProtocol. + */ + +static void ssh_bpp_input_raw_data_callback(void *context) +{ + BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; + ssh_bpp_handle_input(bpp); +} + +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, const char *fingerprint, ssh_key *key) +{ + if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) + return -1; /* no manual keys configured */ + + if (fingerprint) { + /* + * The 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 colon-separated hex block at the + * end of the string. + */ + const char *p = strrchr(fingerprint, ' '); + fingerprint = p ? p+1 : fingerprint; + /* Quick sanity checks, including making sure it's in lowercase */ + assert(strlen(fingerprint) == 16*3 - 1); + assert(fingerprint[2] == ':'); + assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0); + + 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)); + pq_pop(ppl->in_pq); + 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, + struct RSAKey *hostkey, struct RSAKey *servkey) +{ + struct MD5Context md5c; + int i; + + MD5Init(&md5c); + for (i = (bignum_bitcount(hostkey->modulus) + 7) / 8; i-- ;) + put_byte(&md5c, bignum_byte(hostkey->modulus, i)); + for (i = (bignum_bitcount(servkey->modulus) + 7) / 8; i-- ;) + put_byte(&md5c, bignum_byte(servkey->modulus, i)); + put_data(&md5c, cookie, 8); + MD5Final(session_id, &md5c); +} + +/* ---------------------------------------------------------------------- + * 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/sshcr.h b/sshcr.h new file mode 100644 index 00000000..5abf4c17 --- /dev/null +++ b/sshcr.h @@ -0,0 +1,85 @@ +/* + * Coroutine mechanics used in PuTTY's SSH code. + */ + +#ifndef PUTTY_SSHCR_H +#define PUTTY_SSHCR_H + +/* + * If these macros look impenetrable to you, you might find it helpful + * to read + * + * https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html + * + * which explains the theory behind these macros. + * + * In particular, if you are getting `case expression not constant' + * errors when building with MS Visual Studio, this is because MS's + * Edit and Continue debugging feature causes their compiler to + * violate ANSI C. To disable Edit and Continue debugging: + * + * - right-click ssh.c in the FileView + * - click Settings + * - select the C/C++ tab and the General category + * - under `Debug info:', select anything _other_ than `Program + * Database for Edit and Continue'. + */ + +#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; +#define crBeginState crBegin(s->crLine) +#define crStateP(t, v) \ + struct t *s; \ + if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \ + s = (v); +#define crState(t) crStateP(t, ssh->t) +#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } +#define crFinishFree(z) } sfree(s); return (z); } +#define crFinishFreeV } sfree(s); return; } +#define crReturn(z) \ + do {\ + *crLine =__LINE__; return (z); case __LINE__:;\ + } while (0) +#define crReturnV \ + do {\ + *crLine=__LINE__; return; case __LINE__:;\ + } while (0) +#define crStop(z) do{ *crLine = 0; return (z); }while(0) +#define crStopV do{ *crLine = 0; return; }while(0) + +/* + * The crMaybeWaitUntil macros could have been more easily written in + * terms of the simple crReturn above, by writing things like + * + * while (!condition) { crReturn(whatever); } + * + * (or do-while in the case of crWaitUntil). But it's better to do it + * directly by writing _once_ to crLine before first testing the + * condition, because this way it's robust against the condition check + * potentially freeing the entire coroutine state structure as a side + * effect (as long as it also evaluates false if it does that), + * because we don't write into crLine between the condition evaluating + * to false and the 'return' statement. + */ +#define crMaybeWaitUntil(c) \ + do { \ + *crLine =__LINE__; \ + case __LINE__: if (!(c)) return 0; \ + } while (0) +#define crMaybeWaitUntilV(c) \ + do { \ + *crLine =__LINE__; \ + case __LINE__: if (!(c)) return; \ + } while (0) +#define crWaitUntil(c) \ + do { \ + *crLine =__LINE__; return; \ + case __LINE__: if (!(c)) return 0; \ + } while (0) +#define crWaitUntilV(c) \ + do { \ + *crLine =__LINE__; return; \ + case __LINE__: if (!(c)) return; \ + } while (0) + +#endif /* PUTTY_SSHCR_H */ diff --git a/sshcrcda.c b/sshcrcda.c index 8d77cbb6..ddc70ab8 100644 --- a/sshcrcda.c +++ b/sshcrcda.c @@ -25,16 +25,13 @@ #include "misc.h" #include "ssh.h" -typedef unsigned char uchar; -typedef unsigned short uint16; - /* SSH Constants */ #define SSH_MAXBLOCKS (32 * 1024) #define SSH_BLOCKSIZE (8) /* Hashing constants */ #define HASH_MINSIZE (8 * 1024) -#define HASH_ENTRYSIZE (sizeof(uint16)) +#define HASH_ENTRYSIZE (sizeof(uint16_t)) #define HASH_FACTOR(x) ((x)*3/2) #define HASH_UNUSEDCHAR (0xff) #define HASH_UNUSED (0xffff) @@ -47,15 +44,15 @@ typedef unsigned short uint16; #define CMP(a, b) (memcmp(a, b, SSH_BLOCKSIZE)) -uchar ONE[4] = { 1, 0, 0, 0 }; -uchar ZERO[4] = { 0, 0, 0, 0 }; +uint8_t ONE[4] = { 1, 0, 0, 0 }; +uint8_t ZERO[4] = { 0, 0, 0, 0 }; struct crcda_ctx { - uint16 *h; - uint32 n; + uint16_t *h; + uint32_t n; }; -void *crcda_make_context(void) +struct crcda_ctx *crcda_make_context(void) { struct crcda_ctx *ret = snew(struct crcda_ctx); ret->h = NULL; @@ -63,9 +60,8 @@ void *crcda_make_context(void) return ret; } -void crcda_free_context(void *handle) +void crcda_free_context(struct crcda_ctx *ctx) { - struct crcda_ctx *ctx = (struct crcda_ctx *)handle; if (ctx) { sfree(ctx->h); ctx->h = NULL; @@ -73,16 +69,16 @@ void crcda_free_context(void *handle) } } -static void crc_update(uint32 *a, void *b) +static void crc_update(uint32_t *a, void *b) { *a = crc32_update(*a, b, 4); } /* detect if a block is used in a particular pattern */ -static int check_crc(uchar *S, uchar *buf, uint32 len, uchar *IV) +static bool check_crc(uint8_t *S, uint8_t *buf, uint32_t len, uint8_t *IV) { - uint32 crc; - uchar *c; + uint32_t crc; + uint8_t *c; crc = 0; if (IV && !CMP(S, IV)) { @@ -102,13 +98,13 @@ static int check_crc(uchar *S, uchar *buf, uint32 len, uchar *IV) } /* Detect a crc32 compensation attack on a packet */ -int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV) +bool detect_attack( + struct crcda_ctx *ctx, uint8_t *buf, uint32_t len, uint8_t *IV) { - struct crcda_ctx *ctx = (struct crcda_ctx *)handle; - register uint32 i, j; - uint32 l; - register uchar *c; - uchar *d; + register uint32_t i, j; + uint32_t l; + register uint8_t *c; + uint8_t *d; assert(!(len > (SSH_MAXBLOCKS * SSH_BLOCKSIZE) || len % SSH_BLOCKSIZE != 0)); @@ -117,11 +113,11 @@ int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV) if (ctx->h == NULL) { ctx->n = l; - ctx->h = snewn(ctx->n, uint16); + ctx->h = snewn(ctx->n, uint16_t); } else { if (l > ctx->n) { ctx->n = l; - ctx->h = sresize(ctx->h, ctx->n, uint16); + ctx->h = sresize(ctx->h, ctx->n, uint16_t); } } @@ -129,20 +125,20 @@ int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV) for (c = buf; c < buf + len; c += SSH_BLOCKSIZE) { if (IV && (!CMP(c, IV))) { if ((check_crc(c, buf, len, IV))) - return 1; /* attack detected */ + 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 1; /* attack detected */ + return true; /* attack detected */ else break; } } } - return 0; /* ok */ + return false; /* ok */ } memset(ctx->h, HASH_UNUSEDCHAR, ctx->n * HASH_ENTRYSIZE); @@ -155,18 +151,18 @@ int detect_attack(void *handle, uchar *buf, uint32 len, uchar *IV) if (ctx->h[i] == HASH_IV) { if (!CMP(c, IV)) { if (check_crc(c, buf, len, IV)) - return 1; /* attack detected */ + return true; /* attack detected */ else break; } } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) { if (check_crc(c, buf, len, IV)) - return 1; /* attack detected */ + return true; /* attack detected */ else break; } } ctx->h[i] = j; } - return 0; /* ok */ + return false; /* ok */ } diff --git a/sshdes.c b/sshdes.c index 13487fcd..3c5953f1 100644 --- a/sshdes.c +++ b/sshdes.c @@ -277,16 +277,16 @@ */ typedef struct { - word32 k0246[16], k1357[16]; - word32 iv0, iv1; + uint32_t k0246[16], k1357[16]; + uint32_t iv0, iv1; } DESContext; #define rotl(x, c) ( (x << c) | (x >> (32-c)) ) #define rotl28(x, c) ( ( (x << c) | (x >> (28-c)) ) & 0x0FFFFFFF) -static word32 bitsel(word32 * input, const int *bitnums, int size) +static uint32_t bitsel(uint32_t *input, const int *bitnums, int size) { - word32 ret = 0; + uint32_t ret = 0; while (size--) { int bitpos = *bitnums++; ret <<= 1; @@ -296,7 +296,8 @@ static word32 bitsel(word32 * input, const int *bitnums, int size) return ret; } -static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched) +static void des_key_setup( + uint32_t key_msw, uint32_t key_lsw, DESContext *sched) { static const int PC1_Cbits[] = { @@ -326,8 +327,8 @@ static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched) static const int leftshifts[] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; - word32 C, D; - word32 buf[2]; + uint32_t C, D; + uint32_t buf[2]; int i; buf[0] = key_lsw; @@ -348,7 +349,7 @@ static void des_key_setup(word32 key_msw, word32 key_lsw, DESContext * sched) sched->iv0 = sched->iv1 = 0; } -static const word32 SPboxes[8][64] = { +static const uint32_t SPboxes[8][64] = { {0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004, 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, @@ -520,10 +521,10 @@ static const word32 SPboxes[8][64] = { bitswap(R, L, 16, 0x0000FFFF), \ bitswap(R, L, 4, 0x0F0F0F0F)) -static void des_encipher(word32 * output, word32 L, word32 R, - DESContext * sched) +static void des_encipher( + uint32_t *output, uint32_t L, uint32_t R, DESContext *sched) { - word32 swap, s0246, s1357; + uint32_t swap, s0246, s1357; IP(L, R); @@ -560,10 +561,10 @@ static void des_encipher(word32 * output, word32 L, word32 R, output[1] = R; } -static void des_decipher(word32 * output, word32 L, word32 R, - DESContext * sched) +static void des_decipher( + uint32_t *output, uint32_t L, uint32_t R, DESContext *sched) { - word32 swap, s0246, s1357; + uint32_t swap, s0246, s1357; IP(L, R); @@ -603,7 +604,7 @@ static void des_decipher(word32 * output, word32 L, word32 R, static void des_cbc_encrypt(unsigned char *blk, unsigned int len, DESContext * sched) { - word32 out[2], iv0, iv1; + uint32_t out[2], iv0, iv1; unsigned int i; assert((len & 7) == 0); @@ -627,7 +628,7 @@ static void des_cbc_encrypt(unsigned char *blk, static void des_cbc_decrypt(unsigned char *blk, unsigned int len, DESContext * sched) { - word32 out[2], iv0, iv1, xL, xR; + uint32_t out[2], iv0, iv1, xL, xR; unsigned int i; assert((len & 7) == 0); @@ -661,7 +662,7 @@ static void des_3cbc_encrypt(unsigned char *blk, static void des_cbc3_encrypt(unsigned char *blk, unsigned int len, DESContext * scheds) { - word32 out[2], iv0, iv1; + uint32_t out[2], iv0, iv1; unsigned int i; assert((len & 7) == 0); @@ -695,7 +696,7 @@ static void des_3cbc_decrypt(unsigned char *blk, static void des_cbc3_decrypt(unsigned char *blk, unsigned int len, DESContext * scheds) { - word32 out[2], iv0, iv1, xL, xR; + uint32_t out[2], iv0, iv1, xL, xR; unsigned int i; assert((len & 7) == 0); @@ -723,7 +724,7 @@ static void des_cbc3_decrypt(unsigned char *blk, static void des_sdctr3(unsigned char *blk, unsigned int len, DESContext * scheds) { - word32 b[2], iv0, iv1, tmp; + uint32_t b[2], iv0, iv1, tmp; unsigned int i; assert((len & 7) == 0); @@ -747,109 +748,207 @@ static void des_sdctr3(unsigned char *blk, scheds->iv1 = iv1; } -static void *des3_make_context(void) +static void des3_key(DESContext *contexts, const void *vkey) { - return snewn(3, DESContext); + const unsigned char *key = (const unsigned char *)vkey; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), &contexts[0]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 8), + GET_32BIT_MSB_FIRST(key + 12), &contexts[1]); + des_key_setup(GET_32BIT_MSB_FIRST(key + 16), + GET_32BIT_MSB_FIRST(key + 20), &contexts[2]); } -static void *des3_ssh1_make_context(void) +static void des_iv(DESContext *context, const void *viv) { - /* Need 3 keys for each direction, in SSH-1 */ - return snewn(6, DESContext); + const unsigned char *iv = (const unsigned char *)viv; + context->iv0 = GET_32BIT_MSB_FIRST(iv); + context->iv1 = GET_32BIT_MSB_FIRST(iv + 4); } -static void *des_make_context(void) +static void des_key(DESContext *context, const void *vkey) { - return snew(DESContext); + const unsigned char *key = (const unsigned char *)vkey; + des_key_setup(GET_32BIT_MSB_FIRST(key), + GET_32BIT_MSB_FIRST(key + 4), context); } -static void *des_ssh1_make_context(void) +struct des3_ssh1_ctx { + /* 3 cipher context for each direction */ + DESContext contexts[6]; + ssh1_cipher vt; +}; + +struct des_ssh1_ctx { + /* 1 cipher context for each direction */ + DESContext contexts[2]; + ssh1_cipher vt; +}; + +static ssh1_cipher *des3_ssh1_new(void) { - /* Need one key for each direction, in SSH-1 */ - return snewn(2, DESContext); + struct des3_ssh1_ctx *ctx = snew(struct des3_ssh1_ctx); + ctx->vt = &ssh1_3des; + return &ctx->vt; } -static void des3_free_context(void *handle) /* used for both 3DES and DES */ +static ssh1_cipher *des_ssh1_new(void) { - sfree(handle); + struct des_ssh1_ctx *ctx = snew(struct des_ssh1_ctx); + ctx->vt = &ssh1_des; + return &ctx->vt; } -static void des3_key(void *handle, unsigned char *key) +static void des3_ssh1_free(ssh1_cipher *cipher) { - DESContext *keys = (DESContext *) handle; - des_key_setup(GET_32BIT_MSB_FIRST(key), - GET_32BIT_MSB_FIRST(key + 4), &keys[0]); - des_key_setup(GET_32BIT_MSB_FIRST(key + 8), - GET_32BIT_MSB_FIRST(key + 12), &keys[1]); - des_key_setup(GET_32BIT_MSB_FIRST(key + 16), - GET_32BIT_MSB_FIRST(key + 20), &keys[2]); + struct des3_ssh1_ctx *ctx = container_of(cipher, struct des3_ssh1_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void des3_iv(void *handle, unsigned char *key) +static void des_ssh1_free(ssh1_cipher *cipher) { - DESContext *keys = (DESContext *) handle; - keys[0].iv0 = GET_32BIT_MSB_FIRST(key); - keys[0].iv1 = GET_32BIT_MSB_FIRST(key + 4); + struct des_ssh1_ctx *ctx = container_of(cipher, struct des_ssh1_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void des_key(void *handle, unsigned char *key) +static void des3_ssh1_sesskey(ssh1_cipher *cipher, const void *key) { - DESContext *keys = (DESContext *) handle; - des_key_setup(GET_32BIT_MSB_FIRST(key), - GET_32BIT_MSB_FIRST(key + 4), &keys[0]); + struct des3_ssh1_ctx *ctx = container_of(cipher, struct des3_ssh1_ctx, vt); + des3_key(ctx->contexts, key); + des3_key(ctx->contexts+3, key); +} + +static void des3_ssh1_encrypt_blk(ssh1_cipher *cipher, void *blk, int len) +{ + struct des3_ssh1_ctx *ctx = container_of(cipher, struct des3_ssh1_ctx, vt); + des_3cbc_encrypt(blk, len, ctx->contexts); +} + +static void des3_ssh1_decrypt_blk(ssh1_cipher *cipher, void *blk, int len) +{ + struct des3_ssh1_ctx *ctx = container_of(cipher, struct des3_ssh1_ctx, vt); + des_3cbc_decrypt(blk, len, ctx->contexts+3); +} + +static void des_ssh1_sesskey(ssh1_cipher *cipher, const void *key) +{ + struct des_ssh1_ctx *ctx = container_of(cipher, struct des_ssh1_ctx, vt); + des_key(ctx->contexts, key); + des_key(ctx->contexts+1, key); } -static void des3_sesskey(void *handle, unsigned char *key) +static void des_ssh1_encrypt_blk(ssh1_cipher *cipher, void *blk, int len) { - DESContext *keys = (DESContext *) handle; - des3_key(keys, key); - des3_key(keys+3, key); + struct des_ssh1_ctx *ctx = container_of(cipher, struct des_ssh1_ctx, vt); + des_cbc_encrypt(blk, len, ctx->contexts); } -static void des3_encrypt_blk(void *handle, unsigned char *blk, int len) +static void des_ssh1_decrypt_blk(ssh1_cipher *cipher, void *blk, int len) +{ + struct des_ssh1_ctx *ctx = container_of(cipher, struct des_ssh1_ctx, vt); + des_cbc_decrypt(blk, len, ctx->contexts+1); +} + +struct des3_ssh2_ctx { + DESContext contexts[3]; + ssh2_cipher vt; +}; + +struct des_ssh2_ctx { + DESContext context; + ssh2_cipher vt; +}; + +static ssh2_cipher *des3_ssh2_new(const struct ssh2_cipheralg *alg) { - DESContext *keys = (DESContext *) handle; - des_3cbc_encrypt(blk, len, keys); + struct des3_ssh2_ctx *ctx = snew(struct des3_ssh2_ctx); + ctx->vt = alg; + return &ctx->vt; } -static void des3_decrypt_blk(void *handle, unsigned char *blk, int len) +static ssh2_cipher *des_ssh2_new(const struct ssh2_cipheralg *alg) { - DESContext *keys = (DESContext *) handle; - des_3cbc_decrypt(blk, len, keys+3); + struct des_ssh2_ctx *ctx = snew(struct des_ssh2_ctx); + ctx->vt = alg; + return &ctx->vt; } -static void des3_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +static void des3_ssh2_free(ssh2_cipher *cipher) { - DESContext *keys = (DESContext *) handle; - des_cbc3_encrypt(blk, len, keys); + struct des3_ssh2_ctx *ctx = container_of(cipher, struct des3_ssh2_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void des3_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +static void des_ssh2_free(ssh2_cipher *cipher) { - DESContext *keys = (DESContext *) handle; - des_cbc3_decrypt(blk, len, keys); + struct des_ssh2_ctx *ctx = container_of(cipher, struct des_ssh2_ctx, vt); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void des3_ssh2_sdctr(void *handle, unsigned char *blk, int len) +static void des3_ssh2_setiv(ssh2_cipher *cipher, const void *iv) { - DESContext *keys = (DESContext *) handle; - des_sdctr3(blk, len, keys); + struct des3_ssh2_ctx *ctx = container_of(cipher, struct des3_ssh2_ctx, vt); + des_iv(&ctx->contexts[0], iv); + /* SSH-2 treats triple-DES as a single block cipher to wrap in + * CBC, so there's only one IV required, not three */ } -static void des_ssh2_encrypt_blk(void *handle, unsigned char *blk, int len) +static void des3_ssh2_setkey(ssh2_cipher *cipher, const void *key) { - DESContext *keys = (DESContext *) handle; - des_cbc_encrypt(blk, len, keys); + struct des3_ssh2_ctx *ctx = container_of(cipher, struct des3_ssh2_ctx, vt); + des3_key(ctx->contexts, key); } -static void des_ssh2_decrypt_blk(void *handle, unsigned char *blk, int len) +static void des_ssh2_setiv(ssh2_cipher *cipher, const void *iv) { - DESContext *keys = (DESContext *) handle; - des_cbc_decrypt(blk, len, keys); + struct des_ssh2_ctx *ctx = container_of(cipher, struct des_ssh2_ctx, vt); + des_iv(&ctx->context, iv); } -void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +static void des_ssh2_setkey(ssh2_cipher *cipher, const void *key) { + struct des_ssh2_ctx *ctx = container_of(cipher, struct des_ssh2_ctx, vt); + des_key(&ctx->context, key); +} + +static void des3_ssh2_encrypt_blk(ssh2_cipher *cipher, void *blk, int len) +{ + struct des3_ssh2_ctx *ctx = container_of(cipher, struct des3_ssh2_ctx, vt); + des_cbc3_encrypt(blk, len, ctx->contexts); +} + +static void des3_ssh2_decrypt_blk(ssh2_cipher *cipher, void *blk, int len) +{ + struct des3_ssh2_ctx *ctx = container_of(cipher, struct des3_ssh2_ctx, vt); + des_cbc3_decrypt(blk, len, ctx->contexts); +} + +static void des3_ssh2_sdctr(ssh2_cipher *cipher, void *blk, int len) +{ + struct des3_ssh2_ctx *ctx = container_of(cipher, struct des3_ssh2_ctx, vt); + des_sdctr3(blk, len, ctx->contexts); +} + +static void des_ssh2_encrypt_blk(ssh2_cipher *cipher, void *blk, int len) +{ + struct des_ssh2_ctx *ctx = container_of(cipher, struct des_ssh2_ctx, vt); + des_cbc_encrypt(blk, len, &ctx->context); +} + +static void des_ssh2_decrypt_blk(ssh2_cipher *cipher, void *blk, int len) +{ + struct des_ssh2_ctx *ctx = container_of(cipher, struct des_ssh2_ctx, vt); + des_cbc_decrypt(blk, len, &ctx->context); +} + +void des3_decrypt_pubkey(const void *vkey, void *vblk, int len) +{ + const unsigned char *key = (const unsigned char *)vkey; + unsigned char *blk = (unsigned char *)vblk; DESContext ourkeys[3]; des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); @@ -861,8 +960,10 @@ void des3_decrypt_pubkey(unsigned char *key, unsigned char *blk, int len) smemclr(ourkeys, sizeof(ourkeys)); } -void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) +void des3_encrypt_pubkey(const void *vkey, void *vblk, int len) { + const unsigned char *key = (const unsigned char *)vkey; + unsigned char *blk = (unsigned char *)vblk; DESContext ourkeys[3]; des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); @@ -874,9 +975,12 @@ void des3_encrypt_pubkey(unsigned char *key, unsigned char *blk, int len) smemclr(ourkeys, sizeof(ourkeys)); } -void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, - unsigned char *blk, int len) +void des3_decrypt_pubkey_ossh(const void *vkey, const void *viv, + void *vblk, int len) { + const unsigned char *key = (const unsigned char *)vkey; + const unsigned char *iv = (const unsigned char *)viv; + unsigned char *blk = (unsigned char *)vblk; DESContext ourkeys[3]; des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); @@ -890,9 +994,12 @@ void des3_decrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, smemclr(ourkeys, sizeof(ourkeys)); } -void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, - unsigned char *blk, int len) +void des3_encrypt_pubkey_ossh(const void *vkey, const void *viv, + void *vblk, int len) { + const unsigned char *key = (const unsigned char *)vkey; + const unsigned char *iv = (const unsigned char *)viv; + unsigned char *blk = (unsigned char *)vblk; DESContext ourkeys[3]; des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), &ourkeys[0]); @@ -906,8 +1013,9 @@ void des3_encrypt_pubkey_ossh(unsigned char *key, unsigned char *iv, smemclr(ourkeys, sizeof(ourkeys)); } -static void des_keysetup_xdmauth(const unsigned char *keydata, DESContext *dc) +static void des_keysetup_xdmauth(const void *vkeydata, DESContext *dc) { + const unsigned char *keydata = (const unsigned char *)vkeydata; unsigned char key[8]; int i, nbits, j; unsigned int bits; @@ -929,32 +1037,30 @@ static void des_keysetup_xdmauth(const unsigned char *keydata, DESContext *dc) des_key_setup(GET_32BIT_MSB_FIRST(key), GET_32BIT_MSB_FIRST(key + 4), dc); } -void des_encrypt_xdmauth(const unsigned char *keydata, - unsigned char *blk, int len) +void des_encrypt_xdmauth(const void *keydata, void *blk, int len) { DESContext dc; des_keysetup_xdmauth(keydata, &dc); des_cbc_encrypt(blk, len, &dc); } -void des_decrypt_xdmauth(const unsigned char *keydata, - unsigned char *blk, int len) +void des_decrypt_xdmauth(const void *keydata, void *blk, int len) { DESContext dc; des_keysetup_xdmauth(keydata, &dc); des_cbc_decrypt(blk, len, &dc); } -static const struct ssh2_cipher ssh_3des_ssh2 = { - des3_make_context, des3_free_context, des3_iv, des3_key, +static const struct ssh2_cipheralg ssh_3des_ssh2 = { + des3_ssh2_new, des3_ssh2_free, des3_ssh2_setiv, des3_ssh2_setkey, des3_ssh2_encrypt_blk, des3_ssh2_decrypt_blk, NULL, NULL, "3des-cbc", 8, 168, 24, SSH_CIPHER_IS_CBC, "triple-DES CBC", NULL }; -static const struct ssh2_cipher ssh_3des_ssh2_ctr = { - des3_make_context, des3_free_context, des3_iv, des3_key, +static const struct ssh2_cipheralg ssh_3des_ssh2_ctr = { + des3_ssh2_new, des3_ssh2_free, des3_ssh2_setiv, des3_ssh2_setkey, des3_ssh2_sdctr, des3_ssh2_sdctr, NULL, NULL, "3des-ctr", 8, 168, 24, 0, "triple-DES SDCTR", @@ -969,23 +1075,23 @@ static const struct ssh2_cipher ssh_3des_ssh2_ctr = { * apparently aren't the only people to do so, so we sigh * and implement it anyway. */ -static const struct ssh2_cipher ssh_des_ssh2 = { - des_make_context, des3_free_context, des3_iv, des_key, +static const struct ssh2_cipheralg ssh_des_ssh2 = { + des_ssh2_new, des_ssh2_free, des_ssh2_setiv, des_ssh2_setkey, des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, NULL, NULL, "des-cbc", 8, 56, 8, SSH_CIPHER_IS_CBC, "single-DES CBC", NULL }; -static const struct ssh2_cipher ssh_des_sshcom_ssh2 = { - des_make_context, des3_free_context, des3_iv, des_key, +static const struct ssh2_cipheralg ssh_des_sshcom_ssh2 = { + des_ssh2_new, des_ssh2_free, des_ssh2_setiv, des_ssh2_setkey, des_ssh2_encrypt_blk, des_ssh2_decrypt_blk, NULL, NULL, "des-cbc@ssh.com", 8, 56, 8, SSH_CIPHER_IS_CBC, "single-DES CBC", NULL }; -static const struct ssh2_cipher *const des3_list[] = { +static const struct ssh2_cipheralg *const des3_list[] = { &ssh_3des_ssh2_ctr, &ssh_3des_ssh2 }; @@ -995,7 +1101,7 @@ const struct ssh2_ciphers ssh2_3des = { des3_list }; -static const struct ssh2_cipher *const des_list[] = { +static const struct ssh2_cipheralg *const des_list[] = { &ssh_des_ssh2, &ssh_des_sshcom_ssh2 }; @@ -1005,34 +1111,15 @@ const struct ssh2_ciphers ssh2_des = { des_list }; -const struct ssh_cipher ssh_3des = { - des3_ssh1_make_context, des3_free_context, des3_sesskey, - des3_encrypt_blk, des3_decrypt_blk, +const struct ssh1_cipheralg ssh1_3des = { + des3_ssh1_new, des3_ssh1_free, des3_ssh1_sesskey, + des3_ssh1_encrypt_blk, des3_ssh1_decrypt_blk, 8, "triple-DES inner-CBC" }; -static void des_sesskey(void *handle, unsigned char *key) -{ - DESContext *keys = (DESContext *) handle; - des_key(keys, key); - des_key(keys+1, key); -} - -static void des_encrypt_blk(void *handle, unsigned char *blk, int len) -{ - DESContext *keys = (DESContext *) handle; - des_cbc_encrypt(blk, len, keys); -} - -static void des_decrypt_blk(void *handle, unsigned char *blk, int len) -{ - DESContext *keys = (DESContext *) handle; - des_cbc_decrypt(blk, len, keys+1); -} - -const struct ssh_cipher ssh_des = { - des_ssh1_make_context, des3_free_context, des_sesskey, - des_encrypt_blk, des_decrypt_blk, +const struct ssh1_cipheralg ssh1_des = { + des_ssh1_new, des_ssh1_free, des_ssh1_sesskey, + des_ssh1_encrypt_blk, des_ssh1_decrypt_blk, 8, "single-DES CBC" }; diff --git a/sshdh.c b/sshdh.c index f254bc1d..84173e80 100644 --- a/sshdh.c +++ b/sshdh.c @@ -77,12 +77,18 @@ static const struct dh_extra extra_group14 = { P14, G, lenof(P14), lenof(G), }; +static const struct ssh_kex ssh_diffiehellman_group14_sha256 = { + "diffie-hellman-group14-sha256", "group14", + KEXTYPE_DH, &ssh_sha256, &extra_group14, +}; + static const struct ssh_kex ssh_diffiehellman_group14_sha1 = { "diffie-hellman-group14-sha1", "group14", KEXTYPE_DH, &ssh_sha1, &extra_group14, }; static const struct ssh_kex *const group14_list[] = { + &ssh_diffiehellman_group14_sha256, &ssh_diffiehellman_group14_sha1 }; @@ -115,6 +121,46 @@ const struct ssh_kexes ssh_diffiehellman_gex = { 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 struct ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { + "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, + KEXTYPE_GSS, &ssh_sha1, &extra_gex, +}; + +static const struct ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { + "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", + KEXTYPE_GSS, &ssh_sha1, &extra_group14, +}; + +static const struct ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { + "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", + KEXTYPE_GSS, &ssh_sha1, &extra_group1, +}; + +static const struct ssh_kex *const gssk5_sha1_kex_list[] = { + &ssh_gssk5_diffiehellman_gex_sha1, + &ssh_gssk5_diffiehellman_group14_sha1, + &ssh_gssk5_diffiehellman_group1_sha1 +}; + +const struct ssh_kexes ssh_gssk5_sha1_kex = { + sizeof(gssk5_sha1_kex_list) / sizeof(*gssk5_sha1_kex_list), + gssk5_sha1_kex_list +}; + /* * Variables. */ @@ -132,7 +178,7 @@ static void dh_init(struct dh_ctx *ctx) ctx->x = ctx->e = NULL; } -int dh_is_gex(const struct ssh_kex *kex) +bool dh_is_gex(const struct ssh_kex *kex) { const struct dh_extra *extra = (const struct dh_extra *)kex->extra; return extra->pdata == NULL; @@ -141,7 +187,7 @@ int dh_is_gex(const struct ssh_kex *kex) /* * Initialise DH for a standard group. */ -void *dh_setup_group(const struct ssh_kex *kex) +struct dh_ctx *dh_setup_group(const struct ssh_kex *kex) { const struct dh_extra *extra = (const struct dh_extra *)kex->extra; struct dh_ctx *ctx = snew(struct dh_ctx); @@ -154,7 +200,7 @@ void *dh_setup_group(const struct ssh_kex *kex) /* * Initialise DH for a server-supplied group. */ -void *dh_setup_gex(Bignum pval, Bignum gval) +struct dh_ctx *dh_setup_gex(Bignum pval, Bignum gval) { struct dh_ctx *ctx = snew(struct dh_ctx); ctx->p = copybn(pval); @@ -166,9 +212,8 @@ void *dh_setup_gex(Bignum pval, Bignum gval) /* * Clean up and free a context. */ -void dh_cleanup(void *handle) +void dh_cleanup(struct dh_ctx *ctx) { - struct dh_ctx *ctx = (struct dh_ctx *)handle; freebn(ctx->x); freebn(ctx->e); freebn(ctx->p); @@ -193,15 +238,14 @@ void dh_cleanup(void *handle) * Advances in Cryptology: Proceedings of Eurocrypt '96 * Springer-Verlag, May 1996. */ -Bignum dh_create_e(void *handle, int nbits) +Bignum dh_create_e(struct dh_ctx *ctx, int nbits) { - struct dh_ctx *ctx = (struct dh_ctx *)handle; int i; int nbytes; unsigned char *buf; - nbytes = ssh1_bignum_length(ctx->qmask); + nbytes = (bignum_bitcount(ctx->qmask) + 7) / 8; buf = snewn(nbytes, unsigned char); do { @@ -212,10 +256,9 @@ Bignum dh_create_e(void *handle, int nbits) if (ctx->x) freebn(ctx->x); if (nbits == 0 || nbits > bignum_bitcount(ctx->qmask)) { - ssh1_write_bignum(buf, ctx->qmask); - for (i = 2; i < nbytes; i++) - buf[i] &= random_byte(); - ssh1_read_bignum(buf, nbytes, &ctx->x); /* can't fail */ + for (i = 0; i < nbytes; i++) + buf[i] = bignum_byte(ctx->qmask, i) & random_byte(); + ctx->x = bignum_from_bytes(buf, nbytes); } else { int b, nb; ctx->x = bn_power_2(nbits); @@ -250,9 +293,8 @@ Bignum dh_create_e(void *handle, int nbits) * they lead to obviously weak keys that even a passive eavesdropper * can figure out.) */ -const char *dh_validate_f(void *handle, Bignum f) +const char *dh_validate_f(struct dh_ctx *ctx, Bignum f) { - struct dh_ctx *ctx = (struct dh_ctx *)handle; if (bignum_cmp(f, One) <= 0) { return "f value received is too small"; } else { @@ -268,9 +310,8 @@ const char *dh_validate_f(void *handle, Bignum f) /* * DH stage 2: given a number f, compute K = f^x mod p. */ -Bignum dh_find_K(void *handle, Bignum f) +Bignum dh_find_K(struct dh_ctx *ctx, Bignum f) { - struct dh_ctx *ctx = (struct dh_ctx *)handle; Bignum ret; ret = modpow(f, ctx->x, ctx->p); return ret; diff --git a/sshdss.c b/sshdss.c index 20a5e7fa..1ed1a2ad 100644 --- a/sshdss.c +++ b/sshdss.c @@ -9,125 +9,38 @@ #include "ssh.h" #include "misc.h" -static void sha_mpint(SHA_State * s, Bignum b) -{ - unsigned char lenbuf[4]; - int len; - len = (bignum_bitcount(b) + 8) / 8; - PUT_32BIT(lenbuf, len); - SHA_Bytes(s, lenbuf, 4); - while (len-- > 0) { - lenbuf[0] = bignum_byte(b, len); - SHA_Bytes(s, lenbuf, 1); - } - smemclr(lenbuf, sizeof(lenbuf)); -} - -static void sha512_mpint(SHA512_State * s, Bignum b) -{ - unsigned char lenbuf[4]; - int len; - len = (bignum_bitcount(b) + 8) / 8; - PUT_32BIT(lenbuf, len); - SHA512_Bytes(s, lenbuf, 4); - while (len-- > 0) { - lenbuf[0] = bignum_byte(b, len); - SHA512_Bytes(s, lenbuf, 1); - } - smemclr(lenbuf, sizeof(lenbuf)); -} +static void dss_freekey(ssh_key *key); /* forward reference */ -static void getstring(const char **data, int *datalen, - const char **p, int *length) +static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) { - *p = NULL; - if (*datalen < 4) - return; - *length = toint(GET_32BIT(*data)); - if (*length < 0) - return; - *datalen -= 4; - *data += 4; - if (*datalen < *length) - return; - *p = *data; - *data += *length; - *datalen -= *length; -} -static Bignum getmp(const char **data, int *datalen) -{ - const char *p; - int length; - Bignum b; + BinarySource src[1]; + struct dss_key *dss; - getstring(data, datalen, &p, &length); - if (!p) + BinarySource_BARE_INIT(src, data.ptr, data.len); + if (!ptrlen_eq_string(get_string(src), "ssh-dss")) return NULL; - if (p[0] & 0x80) - return NULL; /* negative mp */ - b = bignum_from_bytes((const unsigned char *)p, length); - return b; -} - -static Bignum get160(const char **data, int *datalen) -{ - Bignum b; - - if (*datalen < 20) - return NULL; - - b = bignum_from_bytes((const unsigned char *)*data, 20); - *data += 20; - *datalen -= 20; - - return b; -} - -static void dss_freekey(void *key); /* forward reference */ - -static void *dss_newkey(const struct ssh_signkey *self, - const char *data, int len) -{ - const char *p; - int slen; - struct dss_key *dss; dss = snew(struct dss_key); - getstring(&data, &len, &p, &slen); - -#ifdef DEBUG_DSS - { - int i; - printf("key:"); - for (i = 0; i < len; i++) - printf(" %02x", (unsigned char) (data[i])); - printf("\n"); - } -#endif - - if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { - sfree(dss); - return NULL; - } - dss->p = getmp(&data, &len); - dss->q = getmp(&data, &len); - dss->g = getmp(&data, &len); - dss->y = getmp(&data, &len); + dss->sshk = &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 (!dss->p || !dss->q || !dss->g || !dss->y || + if (get_err(src) || !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { /* Invalid key. */ - dss_freekey(dss); + dss_freekey(&dss->sshk); return NULL; } - return dss; + return &dss->sshk; } -static void dss_freekey(void *key) +static void dss_freekey(ssh_key *key) { - struct dss_key *dss = (struct dss_key *) key; + struct dss_key *dss = container_of(key, struct dss_key, sshk); if (dss->p) freebn(dss->p); if (dss->q) @@ -141,9 +54,9 @@ static void dss_freekey(void *key) sfree(dss); } -static char *dss_fmtkey(void *key) +static char *dss_cache_str(ssh_key *key) { - struct dss_key *dss = (struct dss_key *) key; + struct dss_key *dss = container_of(key, struct dss_key, sshk); char *p; int len, i, pos, nibbles; static const char hex[] = "0123456789abcdef"; @@ -191,28 +104,19 @@ static char *dss_fmtkey(void *key) return p; } -static int dss_verifysig(void *key, const char *sig, int siglen, - const char *data, int datalen) +static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) { - struct dss_key *dss = (struct dss_key *) key; - const char *p; - int slen; - char hash[20]; + struct dss_key *dss = container_of(key, struct dss_key, sshk); + BinarySource src[1]; + unsigned char hash[20]; Bignum r, s, w, gu1p, yu2p, gu1yu2p, u1, u2, sha, v; - int ret; + bool toret; if (!dss->p) - return 0; - -#ifdef DEBUG_DSS - { - int i; - printf("sig:"); - for (i = 0; i < siglen; i++) - printf(" %02x", (unsigned char) (sig[i])); - printf("\n"); - } -#endif + return false; + + BinarySource_BARE_INIT(src, sig.ptr, sig.len); + /* * Commercial SSH (2.0.13) and OpenSSH disagree over the format * of a DSA signature. OpenSSH is in line with RFC 4253: @@ -224,27 +128,30 @@ static int dss_verifysig(void *key, const char *sig, int siglen, * the length: length 40 means the commercial-SSH bug, anything * else is assumed to be RFC-compliant. */ - if (siglen != 40) { /* bug not present; read admin fields */ - getstring(&sig, &siglen, &p, &slen); - if (!p || slen != 7 || memcmp(p, "ssh-dss", 7)) { - return 0; - } - sig += 4, siglen -= 4; /* skip yet another length field */ + 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; } - r = get160(&sig, &siglen); - s = get160(&sig, &siglen); + + /* Now we're sitting on a 40-byte string for sure. */ + r = bignum_from_bytes(sig.ptr, 20); + s = bignum_from_bytes((const char *)sig.ptr + 20, 20); if (!r || !s) { if (r) freebn(r); if (s) freebn(s); - return 0; + return false; } if (!bignum_cmp(s, Zero)) { freebn(r); freebn(s); - return 0; + return false; } /* @@ -254,16 +161,14 @@ static int dss_verifysig(void *key, const char *sig, int siglen, if (!w) { freebn(r); freebn(s); - return 0; + return false; } /* * Step 2. u1 <- SHA(message) * w mod q. */ - SHA_Simple(data, datalen, (unsigned char *)hash); - p = hash; - slen = 20; - sha = get160(&p, &slen); + SHA_Simple(data.ptr, data.len, hash); + sha = bignum_from_bytes(hash, 20); u1 = modmul(sha, w, dss->q); /* @@ -283,7 +188,7 @@ static int dss_verifysig(void *key, const char *sig, int siglen, * Step 5. v should now be equal to r. */ - ret = !bignum_cmp(v, r); + toret = !bignum_cmp(v, r); freebn(w); freebn(sha); @@ -296,111 +201,61 @@ static int dss_verifysig(void *key, const char *sig, int siglen, freebn(r); freebn(s); - return ret; + return toret; } -static unsigned char *dss_public_blob(void *key, int *len) +static void dss_public_blob(ssh_key *key, BinarySink *bs) { - struct dss_key *dss = (struct dss_key *) key; - int plen, qlen, glen, ylen, bloblen; - int i; - unsigned char *blob, *p; - - plen = (bignum_bitcount(dss->p) + 8) / 8; - qlen = (bignum_bitcount(dss->q) + 8) / 8; - glen = (bignum_bitcount(dss->g) + 8) / 8; - ylen = (bignum_bitcount(dss->y) + 8) / 8; + struct dss_key *dss = container_of(key, struct dss_key, sshk); - /* - * string "ssh-dss", mpint p, mpint q, mpint g, mpint y. Total - * 27 + sum of lengths. (five length fields, 20+7=27). - */ - bloblen = 27 + plen + qlen + glen + ylen; - blob = snewn(bloblen, unsigned char); - p = blob; - PUT_32BIT(p, 7); - p += 4; - memcpy(p, "ssh-dss", 7); - p += 7; - PUT_32BIT(p, plen); - p += 4; - for (i = plen; i--;) - *p++ = bignum_byte(dss->p, i); - PUT_32BIT(p, qlen); - p += 4; - for (i = qlen; i--;) - *p++ = bignum_byte(dss->q, i); - PUT_32BIT(p, glen); - p += 4; - for (i = glen; i--;) - *p++ = bignum_byte(dss->g, i); - PUT_32BIT(p, ylen); - p += 4; - for (i = ylen; i--;) - *p++ = bignum_byte(dss->y, i); - assert(p == blob + bloblen); - *len = bloblen; - return blob; + 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 unsigned char *dss_private_blob(void *key, int *len) +static void dss_private_blob(ssh_key *key, BinarySink *bs) { - struct dss_key *dss = (struct dss_key *) key; - int xlen, bloblen; - int i; - unsigned char *blob, *p; - - xlen = (bignum_bitcount(dss->x) + 8) / 8; + struct dss_key *dss = container_of(key, struct dss_key, sshk); - /* - * mpint x, string[20] the SHA of p||q||g. Total 4 + xlen. - */ - bloblen = 4 + xlen; - blob = snewn(bloblen, unsigned char); - p = blob; - PUT_32BIT(p, xlen); - p += 4; - for (i = xlen; i--;) - *p++ = bignum_byte(dss->x, i); - assert(p == blob + bloblen); - *len = bloblen; - return blob; + put_mp_ssh2(bs, dss->x); } -static void *dss_createkey(const struct ssh_signkey *self, - const unsigned char *pub_blob, int pub_len, - const unsigned char *priv_blob, int priv_len) +static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) { + BinarySource src[1]; + ssh_key *sshk; struct dss_key *dss; - const char *pb = (const char *) priv_blob; - const char *hash; - int hashlen; + ptrlen hash; SHA_State s; unsigned char digest[20]; Bignum ytest; - dss = dss_newkey(self, (char *) pub_blob, pub_len); - if (!dss) + sshk = dss_new_pub(self, pub); + if (!sshk) return NULL; - dss->x = getmp(&pb, &priv_len); - if (!dss->x) { - dss_freekey(dss); + + dss = container_of(sshk, struct dss_key, sshk); + BinarySource_BARE_INIT(src, priv.ptr, priv.len); + 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. */ - hashlen = -1; - getstring(&pb, &priv_len, &hash, &hashlen); - if (hashlen == 20) { + hash = get_string(src); + if (hash.len == 20) { SHA_Init(&s); - sha_mpint(&s, dss->p); - sha_mpint(&s, dss->q); - sha_mpint(&s, dss->g); + put_mp_ssh2(&s, dss->p); + put_mp_ssh2(&s, dss->q); + put_mp_ssh2(&s, dss->g); SHA_Final(&s, digest); - if (0 != memcmp(hash, digest, 20)) { - dss_freekey(dss); + if (0 != memcmp(hash.ptr, digest, 20)) { + dss_freekey(&dss->sshk); return NULL; } } @@ -410,78 +265,63 @@ static void *dss_createkey(const struct ssh_signkey *self, */ ytest = modpow(dss->g, dss->x, dss->p); if (0 != bignum_cmp(ytest, dss->y)) { - dss_freekey(dss); + dss_freekey(&dss->sshk); freebn(ytest); return NULL; } freebn(ytest); - return dss; + return &dss->sshk; } -static void *dss_openssh_createkey(const struct ssh_signkey *self, - const unsigned char **blob, int *len) +static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) { - const char **b = (const char **) blob; struct dss_key *dss; dss = snew(struct dss_key); + dss->sshk = &ssh_dss; - dss->p = getmp(b, len); - dss->q = getmp(b, len); - dss->g = getmp(b, len); - dss->y = getmp(b, len); - dss->x = getmp(b, len); + 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 (!dss->p || !dss->q || !dss->g || !dss->y || !dss->x || + if (get_err(src) || !bignum_cmp(dss->q, Zero) || !bignum_cmp(dss->p, Zero)) { /* Invalid key. */ - dss_freekey(dss); + dss_freekey(&dss->sshk); return NULL; } - return dss; + return &dss->sshk; } -static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len) +static void dss_openssh_blob(ssh_key *key, BinarySink *bs) { - struct dss_key *dss = (struct dss_key *) key; - int bloblen, i; - - bloblen = - ssh2_bignum_length(dss->p) + - ssh2_bignum_length(dss->q) + - ssh2_bignum_length(dss->g) + - ssh2_bignum_length(dss->y) + - ssh2_bignum_length(dss->x); - - if (bloblen > len) - return bloblen; - - bloblen = 0; -#define ENC(x) \ - PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \ - for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i); - ENC(dss->p); - ENC(dss->q); - ENC(dss->g); - ENC(dss->y); - ENC(dss->x); - - return bloblen; + 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 struct ssh_signkey *self, - const void *blob, int len) +static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) { + ssh_key *sshk; struct dss_key *dss; int ret; - dss = dss_newkey(self, (const char *) blob, len); - if (!dss) + sshk = dss_new_pub(self, pub); + if (!sshk) return -1; + + dss = container_of(sshk, struct dss_key, sshk); ret = bignum_bitcount(dss->p); - dss_freekey(dss); + dss_freekey(&dss->sshk); return ret; } @@ -568,16 +408,16 @@ Bignum *dss_gen_k(const char *id_string, Bignum modulus, Bignum private_key, * Hash some identifying text plus x. */ SHA512_Init(&ss); - SHA512_Bytes(&ss, id_string, strlen(id_string) + 1); - sha512_mpint(&ss, private_key); + put_asciz(&ss, id_string); + put_mp_ssh2(&ss, private_key); SHA512_Final(&ss, digest512); /* * Now hash that digest plus the message hash. */ SHA512_Init(&ss); - SHA512_Bytes(&ss, digest512, sizeof(digest512)); - SHA512_Bytes(&ss, digest, digest_len); + put_data(&ss, digest512, sizeof(digest512)); + put_data(&ss, digest, digest_len); while (1) { SHA512_State ss2 = ss; /* structure copy */ @@ -601,19 +441,18 @@ Bignum *dss_gen_k(const char *id_string, Bignum modulus, Bignum private_key, /* Very unlikely we get here, but if so, k was unsuitable. */ freebn(k); /* Perturb the hash to think of a different k. */ - SHA512_Bytes(&ss, "x", 1); + put_byte(&ss, 'x'); /* Go round and try again. */ } } -static unsigned char *dss_sign(void *key, const char *data, int datalen, - int *siglen) +static void dss_sign(ssh_key *key, const void *data, int datalen, + BinarySink *bs) { - struct dss_key *dss = (struct dss_key *) key; + struct dss_key *dss = container_of(key, struct dss_key, sshk); Bignum k, gkp, hash, kinv, hxr, r, s; unsigned char digest[20]; - unsigned char *bytes; - int nbytes, i; + int i; SHA_Simple(data, datalen, digest); @@ -637,43 +476,31 @@ static unsigned char *dss_sign(void *key, const char *data, int datalen, freebn(k); freebn(hash); - /* - * Signature blob is - * - * string "ssh-dss" - * string two 20-byte numbers r and s, end to end - * - * i.e. 4+7 + 4+40 bytes. - */ - nbytes = 4 + 7 + 4 + 40; - bytes = snewn(nbytes, unsigned char); - PUT_32BIT(bytes, 7); - memcpy(bytes + 4, "ssh-dss", 7); - PUT_32BIT(bytes + 4 + 7, 40); - for (i = 0; i < 20; i++) { - bytes[4 + 7 + 4 + i] = bignum_byte(r, 19 - i); - bytes[4 + 7 + 4 + 20 + i] = bignum_byte(s, 19 - i); - } + put_stringz(bs, "ssh-dss"); + put_uint32(bs, 40); + for (i = 0; i < 20; i++) + put_byte(bs, bignum_byte(r, 19 - i)); + for (i = 0; i < 20; i++) + put_byte(bs, bignum_byte(s, 19 - i)); freebn(r); freebn(s); - - *siglen = nbytes; - return bytes; } -const struct ssh_signkey ssh_dss = { - dss_newkey, +const ssh_keyalg ssh_dss = { + dss_new_pub, + dss_new_priv, + dss_new_priv_openssh, + dss_freekey, - dss_fmtkey, + dss_sign, + dss_verify, dss_public_blob, dss_private_blob, - dss_createkey, - dss_openssh_createkey, - dss_openssh_fmtkey, - 5 /* p,q,g,y,x */, + dss_openssh_blob, + dss_cache_str, + dss_pubkey_bits, - dss_verifysig, - dss_sign, + "ssh-dss", "dss", NULL, diff --git a/sshdsscert.c b/sshdsscert.c new file mode 100644 index 00000000..b8593ec5 --- /dev/null +++ b/sshdsscert.c @@ -0,0 +1,315 @@ +/* + * DSS Certificate implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +/* ----------------------------------------------------------------------- + * Implementation of the ssh-dss-cert-v01@openssh.com key type + */ + +static void dsscert_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *dsscert_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + struct dss_cert_key *certkey; + + BinarySource_BARE_INIT(src, data.ptr, data.len); + ptrlen certtype = get_string(src); + if (!ptrlen_eq_string(certtype, self->ssh_id)) + return NULL; + + certkey = snew(struct dss_cert_key); + memset(certkey, 0, sizeof(struct dss_cert_key)); + certkey->sshk = self; + + certkey->certificate.ptr = snewn(data.len, char); + memcpy((void*)(certkey->certificate.ptr), data.ptr, data.len); + certkey->certificate.len = data.len; + + if (get_err(src)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + certkey->nonce = mkstr(get_string(src)); + certkey->p = get_mp_ssh2(src); + certkey->q = get_mp_ssh2(src); + certkey->g = get_mp_ssh2(src); + certkey->y = get_mp_ssh2(src); + certkey->serial = get_uint64(src); + certkey->type = get_uint32(src); + certkey->keyid = mkstr(get_string(src)); + certkey->principals = mkstr(get_string(src)); + certkey->valid_after = get_uint64(src); + certkey->valid_before = get_uint64(src); + certkey->options = mkstr(get_string(src)); + certkey->extensions = mkstr(get_string(src)); + certkey->reserved = mkstr(get_string(src)); + + ptrlen sigkey = get_string(src); + + certkey->signature = mkstr(get_string(src)); + + if (get_err(src)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } + + if (get_err(sk)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static void dsscert_freekey(ssh_key *key) +{ + struct dss_cert_key *certkey = container_of(key, struct dss_cert_key, sshk); + + if (certkey->certificate.ptr) + sfree((void*)(certkey->certificate.ptr)); + if (certkey->nonce) + sfree(certkey->nonce); + if (certkey->p) + freebn(certkey->p); + if (certkey->q) + freebn(certkey->q); + if (certkey->g) + freebn(certkey->g); + if (certkey->y) + freebn(certkey->y); + if (certkey->keyid) + sfree(certkey->keyid); + if (certkey->principals) + sfree(certkey->principals); + if (certkey->options) + sfree(certkey->options); + if (certkey->extensions) + sfree(certkey->extensions); + if (certkey->reserved) + sfree(certkey->reserved); + if (certkey->sigkey) + ssh_key_free(certkey->sigkey); + if (certkey->signature) + sfree(certkey->signature); + if (certkey->x) + freebn(certkey->x); + + sfree(certkey); +} + +static ssh_key *dsscert_new_priv(const ssh_keyalg *self, + ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + struct dss_cert_key *certkey; + Bignum ytest; + + sshk = ssh_key_new_pub(self, pub); + if (!sshk) { + return NULL; + } + + certkey = container_of(sshk, struct dss_cert_key, sshk); + BinarySource_BARE_INIT(src, priv.ptr, priv.len); + certkey->x = get_mp_ssh2(src); + + if (get_err(src)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + /* validate the key - from sshdss.c */ + ytest = modpow(certkey->g, certkey->x, certkey->p); + if (0 != bignum_cmp(ytest, certkey->y)) { + dsscert_freekey(&certkey->sshk); + freebn(ytest); + return NULL; + } + freebn(ytest); + + return &certkey->sshk; +} + +static ssh_key *dsscert_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + struct dss_cert_key *certkey; + + certkey = snew(struct dss_cert_key); + memset(certkey, 0, sizeof(struct dss_cert_key)); + certkey->sshk = self; + + ptrlen certdata = get_string(src); + certkey->certificate.ptr = snewn(certdata.len, char); + memcpy((void*)(certkey->certificate.ptr), certdata.ptr, certdata.len); + certkey->certificate.len = certdata.len; + certkey->x = get_mp_ssh2(src); + + if (get_err(src)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource cert[1]; + BinarySource_BARE_INIT(cert, certkey->certificate.ptr, certkey->certificate.len); + ptrlen certtype = get_string(cert); + + certkey->nonce = mkstr(get_string(cert)); + certkey->p = get_mp_ssh2(cert); + certkey->q = get_mp_ssh2(cert); + certkey->g = get_mp_ssh2(cert); + certkey->y = get_mp_ssh2(cert); + certkey->serial = get_uint64(cert); + certkey->type = get_uint32(cert); + certkey->keyid = mkstr(get_string(cert)); + certkey->principals = mkstr(get_string(cert)); + certkey->valid_after = get_uint64(cert); + certkey->valid_before = get_uint64(cert); + certkey->options = mkstr(get_string(cert)); + certkey->extensions = mkstr(get_string(cert)); + certkey->reserved = mkstr(get_string(cert)); + + ptrlen sigkey = get_string(cert); + + certkey->signature = mkstr(get_string(cert)); + + /* validate the key - from sshdss.c */ + if (get_err(cert) || !ptrlen_eq_string(certtype, self->ssh_id) + || !bignum_cmp(certkey->q, Zero) || !bignum_cmp(certkey->p, Zero)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } else { + certkey->sigkey = NULL; + } + + if (get_err(sk)) { + dsscert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static void dsscert_sign(ssh_key *key, const void* data, int datalen, + BinarySink *bs) +{ + struct dss_cert_key *certkey = container_of(key, struct dss_cert_key, sshk); + struct dss_key dsskey; + + dsskey.p = certkey->p; + dsskey.q = certkey->q; + dsskey.g = certkey->g; + dsskey.y = certkey->y; + dsskey.x = certkey->x; + dsskey.sshk = &ssh_dss; + + return ssh_key_sign(&dsskey.sshk, data, datalen, bs); +} + +static bool dsscert_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct dss_cert_key *certkey = container_of(key, struct dss_cert_key, sshk); + struct dss_key dsskey; + + dsskey.p = certkey->p; + dsskey.q = certkey->q; + dsskey.g = certkey->g; + dsskey.y = certkey->y; + dsskey.x = certkey->x; + + dsskey.sshk = &ssh_dss; + + return ssh_key_verify(&dsskey.sshk, sig, data); +} + +static void dsscert_public_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_cert_key *certkey = container_of(key, struct dss_cert_key, sshk); + + // copy the certificate + put_data(bs, certkey->certificate.ptr, certkey->certificate.len); +} + +static void dsscert_private_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_cert_key *dss = container_of(key, struct dss_cert_key, sshk); + + put_mp_ssh2(bs, dss->x); +} + +static void dsscert_openssh_blob(ssh_key* key, BinarySink *bs) +{ + // don't return anything. USed only for export, and we don't export certs +} + +// Used just for looking up host keys for now, so skip +static char * dsscert_cache_str(ssh_key *key) +{ + char *p = snewn(1, char); + p[0] = '\0'; + return p; +} + +static int dsscert_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + struct dss_cert_key *certkey; + int ret; + + sshk = ssh_key_new_pub(self, pub); + if (!sshk) + return -1; + + certkey = container_of(sshk, struct dss_cert_key, sshk); + ret = bignum_bitcount(certkey->p); + dsscert_freekey(&certkey->sshk); + + return ret; +} + +const ssh_keyalg ssh_cert_dss = { + dsscert_new_pub, + dsscert_new_priv, + dsscert_new_priv_openssh, + + dsscert_freekey, + dsscert_sign, + dsscert_verify, + dsscert_public_blob, + dsscert_private_blob, + dsscert_openssh_blob, + dsscert_cache_str, + + dsscert_pubkey_bits, + + "ssh-dss-cert-v01@openssh.com", + "ssh-dss-cert-v01", + NULL, +}; diff --git a/sshdssg.c b/sshdssg.c index 3d7b0ef6..882fbc1d 100644 --- a/sshdssg.c +++ b/sshdssg.c @@ -12,6 +12,8 @@ int dsa_generate(struct dss_key *key, int bits, progfn_t pfn, unsigned pfirst, qfirst; int progress; + key->sshk = &ssh_dss; + /* * Set up the phase limits for the progress report. We do this * by passing minus the phase number. diff --git a/sshecc.c b/sshecc.c index e1166827..d94d354f 100644 --- a/sshecc.c +++ b/sshecc.c @@ -64,7 +64,7 @@ static void initialise_wcurve(struct ec_curve *curve, int bits, curve->w.G.x = bignum_from_bytes(Gx, length); curve->w.G.y = bignum_from_bytes(Gy, length); curve->w.G.curve = curve; - curve->w.G.infinity = 0; + curve->w.G.infinity = false; } static void initialise_mcurve(struct ec_curve *curve, int bits, @@ -89,7 +89,7 @@ static void initialise_mcurve(struct ec_curve *curve, int bits, curve->m.G.y = NULL; curve->m.G.z = NULL; curve->m.G.curve = curve; - curve->m.G.infinity = 0; + curve->m.G.infinity = false; } static void initialise_ecurve(struct ec_curve *curve, int bits, @@ -113,13 +113,13 @@ static void initialise_ecurve(struct ec_curve *curve, int bits, curve->e.B.x = bignum_from_bytes(Bx, length); curve->e.B.y = bignum_from_bytes(By, length); curve->e.B.curve = curve; - curve->e.B.infinity = 0; + curve->e.B.infinity = false; } static struct ec_curve *ec_p256(void) { static struct ec_curve curve = { 0 }; - static unsigned char initialised = 0; + static bool initialised = false; if (!initialised) { @@ -164,7 +164,7 @@ static struct ec_curve *ec_p256(void) curve.textname = curve.name = "nistp256"; /* Now initialised, no need to do it again */ - initialised = 1; + initialised = true; } return &curve; @@ -173,7 +173,7 @@ static struct ec_curve *ec_p256(void) static struct ec_curve *ec_p384(void) { static struct ec_curve curve = { 0 }; - static unsigned char initialised = 0; + static bool initialised = false; if (!initialised) { @@ -230,7 +230,7 @@ static struct ec_curve *ec_p384(void) curve.textname = curve.name = "nistp384"; /* Now initialised, no need to do it again */ - initialised = 1; + initialised = true; } return &curve; @@ -239,7 +239,7 @@ static struct ec_curve *ec_p384(void) static struct ec_curve *ec_p521(void) { static struct ec_curve curve = { 0 }; - static unsigned char initialised = 0; + static bool initialised = false; if (!initialised) { @@ -314,7 +314,7 @@ static struct ec_curve *ec_p521(void) curve.textname = curve.name = "nistp521"; /* Now initialised, no need to do it again */ - initialised = 1; + initialised = true; } return &curve; @@ -323,7 +323,7 @@ static struct ec_curve *ec_p521(void) static struct ec_curve *ec_curve25519(void) { static struct ec_curve curve = { 0 }; - static unsigned char initialised = 0; + static bool initialised = false; if (!initialised) { @@ -359,7 +359,7 @@ static struct ec_curve *ec_curve25519(void) curve.textname = "Curve25519"; /* Now initialised, no need to do it again */ - initialised = 1; + initialised = true; } return &curve; @@ -368,7 +368,7 @@ static struct ec_curve *ec_curve25519(void) static struct ec_curve *ec_ed25519(void) { static struct ec_curve curve = { 0 }; - static unsigned char initialised = 0; + static bool initialised = false; if (!initialised) { @@ -411,7 +411,7 @@ static struct ec_curve *ec_ed25519(void) curve.textname = "Ed25519"; /* Now initialised, no need to do it again */ - initialised = 1; + initialised = true; } return &curve; @@ -419,13 +419,13 @@ static struct ec_curve *ec_ed25519(void) /* Return 1 if a is -3 % p, otherwise return 0 * This is used because there are some maths optimisations */ -static int ec_aminus3(const struct ec_curve *curve) +static bool ec_aminus3(const struct ec_curve *curve) { - int ret; + bool ret; Bignum _p; if (curve->type != EC_WEIERSTRASS) { - return 0; + return false; } _p = bignum_add_long(curve->w.a, 3); @@ -512,20 +512,20 @@ void ec_point_free(struct ec_point *point) if (point->x) freebn(point->x); if (point->y) freebn(point->y); if (point->z) freebn(point->z); - point->infinity = 0; + point->infinity = false; sfree(point); } static struct ec_point *ec_point_new(const struct ec_curve *curve, const Bignum x, const Bignum y, const Bignum z, - unsigned char infinity) + bool infinity) { struct ec_point *point = snewn(1, struct ec_point); point->curve = curve; point->x = x; point->y = y; point->z = z; - point->infinity = infinity ? 1 : 0; + point->infinity = infinity; return point; } @@ -539,14 +539,14 @@ static struct ec_point *ec_point_copy(const struct ec_point *a) a->infinity); } -static int ec_point_verify(const struct ec_point *a) +static bool ec_point_verify(const struct ec_point *a) { if (a->infinity) { - return 1; + return true; } else if (a->curve->type == EC_EDWARDS) { /* Check y^2 - x^2 - 1 - d * x^2 * y^2 == 0 */ Bignum y2, x2, tmp, tmp2, tmp3; - int ret; + bool ret; y2 = ecf_square(a->y, a->curve); x2 = ecf_square(a->x, a->curve); @@ -564,7 +564,7 @@ static int ec_point_verify(const struct ec_point *a) return ret; } else if (a->curve->type == EC_WEIERSTRASS) { /* Verify y^2 = x^3 + ax + b */ - int ret = 0; + bool ret = false; Bignum lhs = NULL, x3 = NULL, ax = NULL, x3ax = NULL, x3axm = NULL, x3axb = NULL, rhs = NULL; @@ -586,13 +586,13 @@ static int ec_point_verify(const struct ec_point *a) rhs = bigmod(x3axb, a->curve->p); freebn(x3axb); - ret = bignum_cmp(lhs, rhs) ? 0 : 1; + ret = !bignum_cmp(lhs, rhs); freebn(lhs); freebn(rhs); return ret; } else { - return 0; + return false; } } @@ -600,17 +600,17 @@ static int ec_point_verify(const struct ec_point *a) * Elliptic curve point maths */ -/* Returns 1 on success and 0 on memory error */ -static int ecp_normalise(struct ec_point *a) +/* Returns true on success and false on memory error */ +static bool ecp_normalise(struct ec_point *a) { if (!a) { /* No point */ - return 0; + return false; } if (a->infinity) { /* Point is at infinity - i.e. normalised */ - return 1; + return true; } if (a->curve->type == EC_WEIERSTRASS) { @@ -621,17 +621,17 @@ static int ecp_normalise(struct ec_point *a) if (!a->x || !a->y) { /* No point defined */ - return 0; + return false; } else if (!a->z) { /* Already normalised */ - return 1; + return true; } Z2 = ecf_square(a->z, a->curve); Z2inv = modinv(Z2, a->curve->p); if (!Z2inv) { freebn(Z2); - return 0; + return false; } tx = modmul(a->x, Z2inv, a->curve->p); freebn(Z2inv); @@ -642,7 +642,7 @@ static int ecp_normalise(struct ec_point *a) freebn(Z3); if (!Z3inv) { freebn(tx); - return 0; + return false; } ty = modmul(a->y, Z3inv, a->curve->p); freebn(Z3inv); @@ -653,7 +653,7 @@ static int ecp_normalise(struct ec_point *a) a->y = ty; freebn(a->z); a->z = NULL; - return 1; + return true; } else if (a->curve->type == EC_MONTGOMERY) { /* In Montgomery (X : Z) represents the x co-ord (X / Z, ?) */ @@ -661,15 +661,15 @@ static int ecp_normalise(struct ec_point *a) if (!a->x) { /* No point defined */ - return 0; + return false; } else if (!a->z) { /* Already normalised */ - return 1; + return true; } tmp = modinv(a->z, a->curve->p); if (!tmp) { - return 0; + return false; } tmp2 = modmul(a->x, tmp, a->curve->p); freebn(tmp); @@ -678,23 +678,23 @@ static int ecp_normalise(struct ec_point *a) a->z = NULL; freebn(a->x); a->x = tmp2; - return 1; + return true; } else if (a->curve->type == EC_EDWARDS) { /* Always normalised */ - return 1; + return true; } else { - return 0; + return false; } } -static struct ec_point *ecp_doublew(const struct ec_point *a, const int aminus3) +static struct ec_point *ecp_doublew(const struct ec_point *a, bool aminus3) { Bignum S, M, outx, outy, outz; if (bignum_cmp(a->y, Zero) == 0) { /* Identity */ - return ec_point_new(a->curve, NULL, NULL, NULL, 1); + return ec_point_new(a->curve, NULL, NULL, NULL, true); } /* S = 4*X*Y^2 */ @@ -802,7 +802,7 @@ static struct ec_point *ecp_doublew(const struct ec_point *a, const int aminus3) freebn(YZ); } - return ec_point_new(a->curve, outx, outy, outz, 0); + return ec_point_new(a->curve, outx, outy, outz, false); } static struct ec_point *ecp_doublem(const struct ec_point *a) @@ -865,20 +865,20 @@ static struct ec_point *ecp_doublem(const struct ec_point *a) freebn(tmp); } - return ec_point_new(a->curve, outx, NULL, outz, 0); + return ec_point_new(a->curve, outx, NULL, outz, false); } /* Forward declaration for Edwards curve doubling */ static struct ec_point *ecp_add(const struct ec_point *a, const struct ec_point *b, - const int aminus3); + bool aminus3); -static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3) +static struct ec_point *ecp_double(const struct ec_point *a, bool aminus3) { if (a->infinity) { /* Identity */ - return ec_point_new(a->curve, NULL, NULL, NULL, 1); + return ec_point_new(a->curve, NULL, NULL, NULL, true); } if (a->curve->type == EC_EDWARDS) @@ -897,7 +897,7 @@ static struct ec_point *ecp_double(const struct ec_point *a, const int aminus3) static struct ec_point *ecp_addw(const struct ec_point *a, const struct ec_point *b, - const int aminus3) + bool aminus3) { Bignum U1, U2, S1, S2, outx, outy, outz; @@ -949,7 +949,7 @@ static struct ec_point *ecp_addw(const struct ec_point *a, freebn(S1); freebn(S2); /* Infinity */ - return ec_point_new(a->curve, NULL, NULL, NULL, 1); + return ec_point_new(a->curve, NULL, NULL, NULL, true); } } @@ -1019,7 +1019,7 @@ static struct ec_point *ecp_addw(const struct ec_point *a, } } - return ec_point_new(a->curve, outx, outy, outz, 0); + return ec_point_new(a->curve, outx, outy, outz, false); } static struct ec_point *ecp_addm(const struct ec_point *a, @@ -1070,7 +1070,7 @@ static struct ec_point *ecp_addm(const struct ec_point *a, freebn(tmp2); } - return ec_point_new(a->curve, outx, NULL, outz, 0); + return ec_point_new(a->curve, outx, NULL, outz, false); } static struct ec_point *ecp_adde(const struct ec_point *a, @@ -1135,12 +1135,12 @@ static struct ec_point *ecp_adde(const struct ec_point *a, freebn(tmp2); } - return ec_point_new(a->curve, outx, outy, NULL, 0); + return ec_point_new(a->curve, outx, outy, NULL, false); } static struct ec_point *ecp_add(const struct ec_point *a, const struct ec_point *b, - const int aminus3) + bool aminus3) { if (a->curve != b->curve) { return NULL; @@ -1163,13 +1163,14 @@ static struct ec_point *ecp_add(const struct ec_point *a, return NULL; } -static struct ec_point *ecp_mul_(const struct ec_point *a, const Bignum b, int aminus3) +static struct ec_point *ecp_mul_( + const struct ec_point *a, const Bignum b, bool aminus3) { struct ec_point *A, *ret; int bits, i; A = ec_point_copy(a); - ret = ec_point_new(a->curve, NULL, NULL, NULL, 1); + ret = ec_point_new(a->curve, NULL, NULL, NULL, true); bits = bignum_bitcount(b); for (i = 0; i < bits; ++i) @@ -1209,18 +1210,18 @@ static struct ec_point *ecp_mule(const struct ec_point *a, const Bignum b) int i; struct ec_point *ret; - ret = ec_point_new(a->curve, NULL, NULL, NULL, 1); + ret = ec_point_new(a->curve, NULL, NULL, NULL, true); for (i = bignum_bitcount(b); i >= 0 && ret; --i) { { - struct ec_point *tmp = ecp_double(ret, 0); + struct ec_point *tmp = ecp_double(ret, false); ec_point_free(ret); ret = tmp; } if (ret && bignum_bit(b, i)) { - struct ec_point *tmp = ecp_add(ret, a, 0); + struct ec_point *tmp = ecp_add(ret, a, false); ec_point_free(ret); ret = tmp; } @@ -1235,7 +1236,7 @@ static struct ec_point *ecp_mulm(const struct ec_point *p, const Bignum n) int bits, i; /* P1 <- P and P2 <- [2]P */ - P2 = ecp_double(p, 0); + P2 = ecp_double(p, false); P1 = ec_point_copy(p); /* for i = bits − 2 down to 0 */ @@ -1250,7 +1251,7 @@ static struct ec_point *ecp_mulm(const struct ec_point *p, const Bignum n) P2 = tmp; /* P1 <- [2]P1 */ - tmp = ecp_double(P1, 0); + tmp = ecp_double(P1, false); ec_point_free(P1); P1 = tmp; } @@ -1262,7 +1263,7 @@ static struct ec_point *ecp_mulm(const struct ec_point *p, const Bignum n) P1 = tmp; /* P2 <- [2]P2 */ - tmp = ecp_double(P2, 0); + tmp = ecp_double(P2, false); ec_point_free(P2); P2 = tmp; } @@ -1294,7 +1295,7 @@ static struct ec_point *ecp_summul(const Bignum a, const Bignum b, const struct ec_point *point) { struct ec_point *aG, *bP, *ret; - int aminus3; + bool aminus3; if (point->curve->type != EC_WEIERSTRASS) { return NULL; @@ -1421,10 +1422,8 @@ struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve SHA512_Init(&s); keylen = curve->fieldBits / 8; - for (i = 0; i < keylen; ++i) { - unsigned char b = bignum_byte(privateKey, i); - SHA512_Bytes(&s, &b, 1); - } + for (i = 0; i < keylen; ++i) + put_byte(&s, bignum_byte(privateKey, i)); SHA512_Final(&s, hash); /* The second part is simply turning the hash into a Bignum, @@ -1448,23 +1447,23 @@ struct ec_point *ec_public(const Bignum privateKey, const struct ec_curve *curve * Basic sign and verify routines */ -static int _ecdsa_verify(const struct ec_point *publicKey, - const unsigned char *data, const int dataLen, - const Bignum r, const Bignum s) +static bool _ecdsa_verify(const struct ec_point *publicKey, + const unsigned char *data, const int dataLen, + const Bignum r, const Bignum s) { int z_bits, n_bits; Bignum z; - int valid = 0; + bool valid = false; if (publicKey->curve->type != EC_WEIERSTRASS) { - return 0; + return false; } /* Sanity checks */ if (bignum_cmp(r, Zero) == 0 || bignum_cmp(r, publicKey->curve->w.n) >= 0 || bignum_cmp(s, Zero) == 0 || bignum_cmp(s, publicKey->curve->w.n) >= 0) { - return 0; + return false; } /* z = left most bitlen(curve->n) of data */ @@ -1493,7 +1492,7 @@ static int _ecdsa_verify(const struct ec_point *publicKey, w = modinv(s, publicKey->curve->w.n); if (!w) { freebn(z); - return 0; + return false; } u1 = modmul(z, w, publicKey->curve->w.n); u2 = modmul(r, w, publicKey->curve->w.n); @@ -1504,13 +1503,13 @@ static int _ecdsa_verify(const struct ec_point *publicKey, freebn(u2); if (!tmp) { freebn(z); - return 0; + return false; } x = bigmod(tmp->x, publicKey->curve->w.n); ec_point_free(tmp); - valid = (bignum_cmp(r, x) == 0) ? 1 : 0; + valid = (bignum_cmp(r, x) == 0); freebn(x); } @@ -1592,58 +1591,22 @@ static void _ecdsa_sign(const Bignum privateKey, const struct ec_curve *curve, * Misc functions */ -static void getstring(const char **data, int *datalen, - const char **p, int *length) +Bignum BinarySource_get_mp_le(BinarySource *src) { - *p = NULL; - if (*datalen < 4) - return; - *length = toint(GET_32BIT(*data)); - if (*length < 0) - return; - *datalen -= 4; - *data += 4; - if (*datalen < *length) - return; - *p = *data; - *data += *length; - *datalen -= *length; -} - -static Bignum getmp(const char **data, int *datalen) -{ - const char *p; - int length; - - getstring(data, datalen, &p, &length); - if (!p) - return NULL; - if (p[0] & 0x80) - return NULL; /* negative mp */ - return bignum_from_bytes((unsigned char *)p, length); -} - -static Bignum getmp_le(const char **data, int *datalen) -{ - const char *p; - int length; - - getstring(data, datalen, &p, &length); - if (!p) - return NULL; - return bignum_from_bytes_le((const unsigned char *)p, length); + ptrlen mp_str = get_string(src); + return bignum_from_bytes_le(mp_str.ptr, mp_str.len); } -static int decodepoint_ed(const char *p, int length, struct ec_point *point) +bool decodepoint_ed(const char *p, int length, struct ec_point *point) { /* Got some conversion to do, first read in the y co-ord */ - int negative; + bool negative; point->y = bignum_from_bytes_le((const unsigned char*)p, length); if ((unsigned)bignum_bitcount(point->y) > point->curve->fieldBits) { freebn(point->y); point->y = NULL; - return 0; + return false; } /* Read x bit and then reset it */ negative = bignum_bit(point->y, point->curve->fieldBits - 1); @@ -1655,7 +1618,7 @@ static int decodepoint_ed(const char *p, int length, struct ec_point *point) if (!point->x) { freebn(point->y); point->y = NULL; - return 0; + return false; } if (negative) { Bignum tmp = modsub(point->curve->p, point->x, point->curve->p); @@ -1669,20 +1632,20 @@ static int decodepoint_ed(const char *p, int length, struct ec_point *point) point->x = NULL; freebn(point->y); point->y = NULL; - return 0; + return false; } - return 1; + return true; } -static int decodepoint(const char *p, int length, struct ec_point *point) +static bool decodepoint(const char *p, int length, struct ec_point *point) { if (point->curve->type == EC_EDWARDS) { return decodepoint_ed(p, length, point); } if (length < 1 || p[0] != 0x04) /* Only support uncompressed point */ - return 0; + return false; /* Skip compression flag */ ++p; --length; @@ -1691,12 +1654,12 @@ static int decodepoint(const char *p, int length, struct ec_point *point) point->x = NULL; point->y = NULL; point->z = NULL; - return 0; + return false; } length = length / 2; - point->x = bignum_from_bytes((const unsigned char *)p, length); + point->x = bignum_from_bytes(p, length); p += length; - point->y = bignum_from_bytes((const unsigned char *)p, length); + point->y = bignum_from_bytes(p, length); point->z = NULL; /* Verify the point is on the curve */ @@ -1705,39 +1668,29 @@ static int decodepoint(const char *p, int length, struct ec_point *point) point->x = NULL; freebn(point->y); point->y = NULL; - return 0; + return false; } - return 1; + return true; } -static int getmppoint(const char **data, int *datalen, struct ec_point *point) +bool BinarySource_get_point(BinarySource *src, struct ec_point *point) { - const char *p; - int length; - - getstring(data, datalen, &p, &length); - if (!p) return 0; - return decodepoint(p, length, point); + ptrlen str = get_string(src); + if (get_err(src)) return false; + return decodepoint(str.ptr, str.len, point); } /* ---------------------------------------------------------------------- * Exposed ECDSA interface */ -struct ecsign_extra { - struct ec_curve *(*curve)(void); - const struct ssh_hash *hash; - - /* These fields are used by the OpenSSH PEM format importer/exporter */ - const unsigned char *oid; - int oidlen; -}; - -static void ecdsa_freekey(void *key) +static void ecdsa_freekey(ssh_key *key) { - struct ec_key *ec = (struct ec_key *) key; - if (!ec) return; + struct ec_key *ec; + + if (!key) return; + ec = container_of(key, struct ec_key, sshk); if (ec->publicKey.x) freebn(ec->publicKey.x); @@ -1750,42 +1703,37 @@ static void ecdsa_freekey(void *key) sfree(ec); } -static void *ecdsa_newkey(const struct ssh_signkey *self, - const char *data, int len) +static ssh_key *ecdsa_new_pub(const ssh_keyalg *self, ptrlen data) { const struct ecsign_extra *extra = (const struct ecsign_extra *)self->extra; - const char *p; - int slen; + BinarySource src[1]; struct ec_key *ec; struct ec_curve *curve; - getstring(&data, &len, &p, &slen); + BinarySource_BARE_INIT(src, data.ptr, data.len); + get_string(src); - if (!p) { - return NULL; - } curve = extra->curve(); assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS); /* Curve name is duplicated for Weierstrass form */ if (curve->type == EC_WEIERSTRASS) { - getstring(&data, &len, &p, &slen); - if (!p) return NULL; - if (!match_ssh_id(slen, p, curve->name)) return NULL; + if (!ptrlen_eq_string(get_string(src), curve->name)) + return NULL; } ec = snew(struct ec_key); + ec->sshk = self; - ec->signalg = self; ec->publicKey.curve = curve; - ec->publicKey.infinity = 0; + ec->publicKey.infinity = false; ec->publicKey.x = NULL; ec->publicKey.y = NULL; ec->publicKey.z = NULL; ec->privateKey = NULL; - if (!getmppoint(&data, &len, &ec->publicKey)) { - ecdsa_freekey(ec); + if (!get_point(src, &ec->publicKey)) { + ecdsa_freekey(&ec->sshk); return NULL; } @@ -1793,16 +1741,16 @@ static void *ecdsa_newkey(const struct ssh_signkey *self, bignum_cmp(ec->publicKey.x, curve->p) >= 0 || bignum_cmp(ec->publicKey.y, curve->p) >= 0) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); ec = NULL; } - return ec; + return &ec->sshk; } -static char *ecdsa_fmtkey(void *key) +static char *ecdsa_cache_str(ssh_key *key) { - struct ec_key *ec = (struct ec_key *) key; + struct ec_key *ec = container_of(key, struct ec_key, sshk); char *p; int len, i, pos, nibbles; static const char hex[] = "0123456789abcdef"; @@ -1839,89 +1787,53 @@ static char *ecdsa_fmtkey(void *key) return p; } -static unsigned char *ecdsa_public_blob(void *key, int *len) +static void ecdsa_public_blob(ssh_key *key, BinarySink *bs) { - struct ec_key *ec = (struct ec_key *) key; - int pointlen, bloblen, fullnamelen, namelen; + struct ec_key *ec = container_of(key, struct ec_key, sshk); + int pointlen; int i; - unsigned char *blob, *p; - - fullnamelen = strlen(ec->signalg->name); if (ec->publicKey.curve->type == EC_EDWARDS) { /* Edwards compressed form "ssh-ed25519" point y[:-1] + x[0:1] */ pointlen = ec->publicKey.curve->fieldBits / 8; - /* Can't handle this in our loop */ - if (pointlen < 2) return NULL; - - bloblen = 4 + fullnamelen + 4 + pointlen; - blob = snewn(bloblen, unsigned char); + assert(pointlen >= 2); - p = blob; - PUT_32BIT(p, fullnamelen); - p += 4; - memcpy(p, ec->signalg->name, fullnamelen); - p += fullnamelen; - PUT_32BIT(p, pointlen); - p += 4; + put_stringz(bs, ec->sshk->ssh_id); + put_uint32(bs, pointlen); /* Unset last bit of y and set first bit of x in its place */ - for (i = 0; i < pointlen - 1; ++i) { - *p++ = bignum_byte(ec->publicKey.y, i); - } + for (i = 0; i < pointlen - 1; ++i) + put_byte(bs, bignum_byte(ec->publicKey.y, i)); /* Unset last bit of y and set first bit of x in its place */ - *p = bignum_byte(ec->publicKey.y, i) & 0x7f; - *p++ |= bignum_bit(ec->publicKey.x, 0) << 7; + put_byte(bs, ((bignum_byte(ec->publicKey.y, i) & 0x7f) | + (bignum_bit(ec->publicKey.x, 0) << 7))); } else if (ec->publicKey.curve->type == EC_WEIERSTRASS) { assert(ec->publicKey.curve->name); - namelen = strlen(ec->publicKey.curve->name); pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8; - /* - * string "ecdsa-sha2-", string "", 0x04 point x, y. - */ - bloblen = 4 + fullnamelen + 4 + namelen + 4 + 1 + (pointlen * 2); - blob = snewn(bloblen, unsigned char); - - p = blob; - PUT_32BIT(p, fullnamelen); - p += 4; - memcpy(p, ec->signalg->name, fullnamelen); - p += fullnamelen; - PUT_32BIT(p, namelen); - p += 4; - memcpy(p, ec->publicKey.curve->name, namelen); - p += namelen; - PUT_32BIT(p, (2 * pointlen) + 1); - p += 4; - *p++ = 0x04; - for (i = pointlen; i--;) { - *p++ = bignum_byte(ec->publicKey.x, i); - } - for (i = pointlen; i--;) { - *p++ = bignum_byte(ec->publicKey.y, i); - } + put_stringz(bs, ec->sshk->ssh_id); + put_stringz(bs, ec->publicKey.curve->name); + put_uint32(bs, (2 * pointlen) + 1); + put_byte(bs, 0x04); + for (i = pointlen; i--;) + put_byte(bs, bignum_byte(ec->publicKey.x, i)); + for (i = pointlen; i--;) + put_byte(bs, bignum_byte(ec->publicKey.y, i)); } else { - return NULL; + assert(0 && "Bad key type in ecdsa_public_blob"); } - - assert(p == blob + bloblen); - *len = bloblen; - - return blob; } -static unsigned char *ecdsa_private_blob(void *key, int *len) +static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) { - struct ec_key *ec = (struct ec_key *) key; - int keylen, bloblen; + struct ec_key *ec = container_of(key, struct ec_key, sshk); + int keylen; int i; - unsigned char *blob, *p; - if (!ec->privateKey) return NULL; + assert(ec->privateKey); if (ec->publicKey.curve->type == EC_EDWARDS) { /* Unsigned */ @@ -1931,55 +1843,44 @@ static unsigned char *ecdsa_private_blob(void *key, int *len) keylen = (bignum_bitcount(ec->privateKey) + 8) / 8; } - /* - * mpint privateKey. Total 4 + keylen. - */ - bloblen = 4 + keylen; - blob = snewn(bloblen, unsigned char); - - p = blob; - PUT_32BIT(p, keylen); - p += 4; + put_uint32(bs, keylen); if (ec->publicKey.curve->type == EC_EDWARDS) { /* Little endian */ for (i = 0; i < keylen; ++i) - *p++ = bignum_byte(ec->privateKey, i); + put_byte(bs, bignum_byte(ec->privateKey, i)); } else { for (i = keylen; i--;) - *p++ = bignum_byte(ec->privateKey, i); + put_byte(bs, bignum_byte(ec->privateKey, i)); } - - assert(p == blob + bloblen); - *len = bloblen; - return blob; } -static void *ecdsa_createkey(const struct ssh_signkey *self, - const unsigned char *pub_blob, int pub_len, - const unsigned char *priv_blob, int priv_len) +static ssh_key *ecdsa_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) { + BinarySource src[1]; + ssh_key *sshk; struct ec_key *ec; struct ec_point *publicKey; - const char *pb = (const char *) priv_blob; - ec = (struct ec_key*)ecdsa_newkey(self, (const char *) pub_blob, pub_len); - if (!ec) { + sshk = ecdsa_new_pub(self, pub); + if (!sshk) return NULL; - } + + ec = container_of(sshk, struct ec_key, sshk); + BinarySource_BARE_INIT(src, priv.ptr, priv.len); if (ec->publicKey.curve->type != EC_WEIERSTRASS && ec->publicKey.curve->type != EC_EDWARDS) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return NULL; } if (ec->publicKey.curve->type == EC_EDWARDS) { - ec->privateKey = getmp_le(&pb, &priv_len); + ec->privateKey = get_mp_le(src); } else { - ec->privateKey = getmp(&pb, &priv_len); + ec->privateKey = get_mp_ssh2(src); } if (!ec->privateKey) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return NULL; } @@ -1990,51 +1891,43 @@ static void *ecdsa_createkey(const struct ssh_signkey *self, bignum_cmp(publicKey->x, ec->publicKey.x) || bignum_cmp(publicKey->y, ec->publicKey.y)) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); ec = NULL; } ec_point_free(publicKey); - return ec; + return &ec->sshk; } -static void *ed25519_openssh_createkey(const struct ssh_signkey *self, - const unsigned char **blob, int *len) +static ssh_key *ed25519_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) { struct ec_key *ec; struct ec_point *publicKey; - const char *p, *q; - int plen, qlen; + ptrlen p, q; - getstring((const char**)blob, len, &p, &plen); - if (!p) - { + p = get_string(src); + q = get_string(src); + if (get_err(src) || p.len != 32 || q.len != 64) return NULL; - } ec = snew(struct ec_key); + ec->sshk = self; - ec->signalg = self; ec->publicKey.curve = ec_ed25519(); - ec->publicKey.infinity = 0; + ec->publicKey.infinity = false; ec->privateKey = NULL; ec->publicKey.x = NULL; ec->publicKey.z = NULL; ec->publicKey.y = NULL; - if (!decodepoint_ed(p, plen, &ec->publicKey)) + if (!decodepoint_ed(p.ptr, p.len, &ec->publicKey)) { - ecdsa_freekey(ec); - return NULL; - } - - getstring((const char**)blob, len, &q, &qlen); - if (!q || qlen != 64) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return NULL; } - ec->privateKey = bignum_from_bytes_le((const unsigned char *)q, 32); + ec->privateKey = bignum_from_bytes_le(q.ptr, 32); /* Check that private key generates public key */ publicKey = ec_public(ec->privateKey, ec->publicKey.curve); @@ -2043,7 +1936,7 @@ static void *ed25519_openssh_createkey(const struct ssh_signkey *self, bignum_cmp(publicKey->x, ec->publicKey.x) || bignum_cmp(publicKey->y, ec->publicKey.y)) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); ec = NULL; } ec_point_free(publicKey); @@ -2054,93 +1947,74 @@ static void *ed25519_openssh_createkey(const struct ssh_signkey *self, * correct as well, otherwise the key we think we've imported * won't behave identically to the way OpenSSH would have treated * it. */ - if (plen != 32 || 0 != memcmp(q + 32, p, 32)) { - ecdsa_freekey(ec); + if (0 != memcmp((const char *)q.ptr + 32, p.ptr, 32)) { + ecdsa_freekey(&ec->sshk); return NULL; } - return ec; + return &ec->sshk; } -static int ed25519_openssh_fmtkey(void *key, unsigned char *blob, int len) +static void ed25519_openssh_blob(ssh_key *key, BinarySink *bs) { - struct ec_key *ec = (struct ec_key *) key; + struct ec_key *ec = container_of(key, struct ec_key, sshk); + strbuf *pub; int pointlen; int keylen; - int bloblen; int i; - if (ec->publicKey.curve->type != EC_EDWARDS) { - return 0; - } + assert(ec->publicKey.curve->type == EC_EDWARDS); pointlen = (bignum_bitcount(ec->publicKey.y) + 7) / 8; keylen = (bignum_bitcount(ec->privateKey) + 7) / 8; - bloblen = 4 + pointlen + 4 + keylen + pointlen; - - if (bloblen > len) - return bloblen; /* Encode the public point */ - PUT_32BIT(blob, pointlen); - blob += 4; - - for (i = 0; i < pointlen - 1; ++i) { - *blob++ = bignum_byte(ec->publicKey.y, i); - } + pub = strbuf_new(); + put_uint32(pub, pointlen); + for (i = 0; i < pointlen - 1; ++i) + put_byte(pub, bignum_byte(ec->publicKey.y, i)); /* Unset last bit of y and set first bit of x in its place */ - *blob = bignum_byte(ec->publicKey.y, i) & 0x7f; - *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7; + put_byte(pub, ((bignum_byte(ec->publicKey.y, i) & 0x7f) | + (bignum_bit(ec->publicKey.x, 0) << 7))); - PUT_32BIT(blob, keylen + pointlen); - blob += 4; - for (i = 0; i < keylen; ++i) { - *blob++ = bignum_byte(ec->privateKey, i); - } + put_data(bs, pub->s, pub->len); + + put_uint32(bs, keylen + pointlen); + for (i = 0; i < keylen; ++i) + put_byte(bs, bignum_byte(ec->privateKey, i)); /* Now encode an extra copy of the public point as the second half * of the private key string, as the OpenSSH format for some * reason requires */ - for (i = 0; i < pointlen - 1; ++i) { - *blob++ = bignum_byte(ec->publicKey.y, i); - } - /* Unset last bit of y and set first bit of x in its place */ - *blob = bignum_byte(ec->publicKey.y, i) & 0x7f; - *blob++ |= bignum_bit(ec->publicKey.x, 0) << 7; + put_data(bs, pub->s + 4, pub->len - 4); - return bloblen; + strbuf_free(pub); } -static void *ecdsa_openssh_createkey(const struct ssh_signkey *self, - const unsigned char **blob, int *len) +static ssh_key *ecdsa_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) { const struct ecsign_extra *extra = (const struct ecsign_extra *)self->extra; - const char **b = (const char **) blob; - const char *p; - int slen; struct ec_key *ec; struct ec_curve *curve; struct ec_point *publicKey; - getstring(b, len, &p, &slen); + get_string(src); - if (!p) { - return NULL; - } curve = extra->curve(); assert(curve->type == EC_WEIERSTRASS); ec = snew(struct ec_key); + ec->sshk = self; - ec->signalg = self; ec->publicKey.curve = curve; - ec->publicKey.infinity = 0; + ec->publicKey.infinity = false; ec->publicKey.x = NULL; ec->publicKey.y = NULL; ec->publicKey.z = NULL; - if (!getmppoint(b, len, &ec->publicKey)) { - ecdsa_freekey(ec); + if (!get_point(src, &ec->publicKey)) { + ecdsa_freekey(&ec->sshk); return NULL; } ec->privateKey = NULL; @@ -2149,14 +2023,14 @@ static void *ecdsa_openssh_createkey(const struct ssh_signkey *self, bignum_cmp(ec->publicKey.x, curve->p) >= 0 || bignum_cmp(ec->publicKey.y, curve->p) >= 0) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return NULL; } - ec->privateKey = getmp(b, len); + ec->privateKey = get_mp_ssh2(src); if (ec->privateKey == NULL) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return NULL; } @@ -2164,7 +2038,7 @@ static void *ecdsa_openssh_createkey(const struct ssh_signkey *self, publicKey = ec_public(ec->privateKey, ec->publicKey.curve); if (!publicKey) { - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return NULL; } @@ -2172,153 +2046,125 @@ static void *ecdsa_openssh_createkey(const struct ssh_signkey *self, bignum_cmp(ec->publicKey.y, publicKey->y)) { /* Private key doesn't make the public key on the given curve */ - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); ec_point_free(publicKey); return NULL; } ec_point_free(publicKey); - return ec; + return &ec->sshk; } -static int ecdsa_openssh_fmtkey(void *key, unsigned char *blob, int len) +static void ecdsa_openssh_blob(ssh_key *key, BinarySink *bs) { - struct ec_key *ec = (struct ec_key *) key; + struct ec_key *ec = container_of(key, struct ec_key, sshk); int pointlen; - int namelen; - int bloblen; int i; - if (ec->publicKey.curve->type != EC_WEIERSTRASS) { - return 0; - } + assert(ec->publicKey.curve->type == EC_WEIERSTRASS); pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8; - namelen = strlen(ec->publicKey.curve->name); - bloblen = - 4 + namelen /* nistpXXX */ - + 4 + 1 + (pointlen * 2) /* 0x04 pX pY */ - + ssh2_bignum_length(ec->privateKey); - - if (bloblen > len) - return bloblen; - - bloblen = 0; - PUT_32BIT(blob+bloblen, namelen); - bloblen += 4; - memcpy(blob+bloblen, ec->publicKey.curve->name, namelen); - bloblen += namelen; + put_stringz(bs, ec->publicKey.curve->name); - PUT_32BIT(blob+bloblen, 1 + (pointlen * 2)); - bloblen += 4; - blob[bloblen++] = 0x04; + put_uint32(bs, 1 + (pointlen * 2)); + put_byte(bs, 0x04); for (i = pointlen; i--; ) - blob[bloblen++] = bignum_byte(ec->publicKey.x, i); + put_byte(bs, bignum_byte(ec->publicKey.x, i)); for (i = pointlen; i--; ) - blob[bloblen++] = bignum_byte(ec->publicKey.y, i); + put_byte(bs, bignum_byte(ec->publicKey.y, i)); - pointlen = (bignum_bitcount(ec->privateKey) + 8) / 8; - PUT_32BIT(blob+bloblen, pointlen); - bloblen += 4; - for (i = pointlen; i--; ) - blob[bloblen++] = bignum_byte(ec->privateKey, i); - - return bloblen; + put_mp_ssh2(bs, ec->privateKey); } -static int ecdsa_pubkey_bits(const struct ssh_signkey *self, - const void *blob, int len) +static int ecdsa_pubkey_bits(const ssh_keyalg *self, ptrlen blob) { + ssh_key *sshk; struct ec_key *ec; int ret; - ec = (struct ec_key*)ecdsa_newkey(self, (const char *) blob, len); - if (!ec) + sshk = ecdsa_new_pub(self, blob); + if (!sshk) return -1; + + ec = container_of(sshk, struct ec_key, sshk); ret = ec->publicKey.curve->fieldBits; - ecdsa_freekey(ec); + ecdsa_freekey(&ec->sshk); return ret; } -static int ecdsa_verifysig(void *key, const char *sig, int siglen, - const char *data, int datalen) +static bool ecdsa_verify(ssh_key *key, ptrlen sig, ptrlen data) { - struct ec_key *ec = (struct ec_key *) key; + struct ec_key *ec = container_of(key, struct ec_key, sshk); const struct ecsign_extra *extra = - (const struct ecsign_extra *)ec->signalg->extra; - const char *p; - int slen; - int digestLen; - int ret; + (const struct ecsign_extra *)ec->sshk->extra; + BinarySource src[1]; + ptrlen sigstr; + bool ret; if (!ec->publicKey.x || !ec->publicKey.y || !ec->publicKey.curve) - return 0; + return false; + + BinarySource_BARE_INIT(src, sig.ptr, sig.len); /* Check the signature starts with the algorithm name */ - getstring(&sig, &siglen, &p, &slen); - if (!p) { - return 0; - } - if (!match_ssh_id(slen, p, ec->signalg->name)) { - return 0; - } + if (!ptrlen_eq_string(get_string(src), ec->sshk->ssh_id)) + return false; + + sigstr = get_string(src); + if (get_err(src)) + return false; - getstring(&sig, &siglen, &p, &slen); - if (!p) return 0; if (ec->publicKey.curve->type == EC_EDWARDS) { struct ec_point *r; + int pointlen = ec->publicKey.curve->fieldBits / 8; Bignum s, h; /* Check that the signature is two times the length of a point */ - if (slen != (ec->publicKey.curve->fieldBits / 8) * 2) { - return 0; + if (sigstr.len != pointlen * 2) { + return false; } /* Check it's the 256 bit field so that SHA512 is the correct hash */ if (ec->publicKey.curve->fieldBits != 256) { - return 0; + return false; } /* Get the signature */ - r = ec_point_new(ec->publicKey.curve, NULL, NULL, NULL, 0); + r = ec_point_new(ec->publicKey.curve, NULL, NULL, NULL, false); if (!r) { - return 0; + return false; } - if (!decodepoint(p, ec->publicKey.curve->fieldBits / 8, r)) { + if (!decodepoint(sigstr.ptr, pointlen, r)) { ec_point_free(r); - return 0; + return false; } - s = bignum_from_bytes_le((unsigned char*)p + (ec->publicKey.curve->fieldBits / 8), - ec->publicKey.curve->fieldBits / 8); + s = bignum_from_bytes_le( + (const char *)sigstr.ptr + pointlen, pointlen); /* Get the hash of the encoded value of R + encoded value of pk + message */ { - int i, pointlen; - unsigned char b; + int i; unsigned char digest[512 / 8]; SHA512_State hs; SHA512_Init(&hs); - /* Add encoded r (no need to encode it again, it was in the signature) */ - SHA512_Bytes(&hs, p, ec->publicKey.curve->fieldBits / 8); + /* Add encoded r (no need to encode it again, it was in + * the signature) */ + put_data(&hs, sigstr.ptr, pointlen); /* Encode pk and add it */ - pointlen = ec->publicKey.curve->fieldBits / 8; - for (i = 0; i < pointlen - 1; ++i) { - b = bignum_byte(ec->publicKey.y, i); - SHA512_Bytes(&hs, &b, 1); - } + for (i = 0; i < pointlen - 1; ++i) + put_byte(&hs, bignum_byte(ec->publicKey.y, i)); /* Unset last bit of y and set first bit of x in its place */ - b = bignum_byte(ec->publicKey.y, i) & 0x7f; - b |= bignum_bit(ec->publicKey.x, 0) << 7; - SHA512_Bytes(&hs, &b, 1); + put_byte(&hs, ((bignum_byte(ec->publicKey.y, i) & 0x7f) | + (bignum_bit(ec->publicKey.x, 0) << 7))); /* Add the message itself */ - SHA512_Bytes(&hs, data, datalen); + put_data(&hs, data.ptr, data.len); /* Get the hash */ SHA512_Final(&hs, digest); @@ -2337,7 +2183,7 @@ static int ecdsa_verifysig(void *key, const char *sig, int siglen, if (!lhs) { ec_point_free(r); freebn(h); - return 0; + return false; } /* rhs = r + h*publicKey */ @@ -2346,14 +2192,14 @@ static int ecdsa_verifysig(void *key, const char *sig, int siglen, if (!tmp) { ec_point_free(lhs); ec_point_free(r); - return 0; + return false; } - rhs = ecp_add(r, tmp, 0); + rhs = ecp_add(r, tmp, false); ec_point_free(r); ec_point_free(tmp); if (!rhs) { ec_point_free(lhs); - return 0; + return false; } /* Check the point is the same */ @@ -2361,7 +2207,7 @@ static int ecdsa_verifysig(void *key, const char *sig, int siglen, if (ret) { ret = !bignum_cmp(lhs->y, rhs->y); if (ret) { - ret = 1; + ret = true; } } ec_point_free(lhs); @@ -2370,21 +2216,24 @@ static int ecdsa_verifysig(void *key, const char *sig, int siglen, } else { Bignum r, s; unsigned char digest[512 / 8]; - void *hashctx; + int digestLen; + ssh_hash *hashctx; + + BinarySource_BARE_INIT(src, sigstr.ptr, sigstr.len); - r = getmp(&p, &slen); - if (!r) return 0; - s = getmp(&p, &slen); - if (!s) { + r = get_mp_ssh2(src); + s = get_mp_ssh2(src); + if (get_err(src)) { freebn(r); - return 0; + freebn(s); + return false; } digestLen = extra->hash->hlen; assert(digestLen <= sizeof(digest)); - hashctx = extra->hash->init(); - extra->hash->bytes(hashctx, data, datalen); - extra->hash->final(hashctx, digest); + hashctx = ssh_hash_new(extra->hash); + put_data(hashctx, data.ptr, data.len); + ssh_hash_final(hashctx, digest); /* Verify the signature */ ret = _ecdsa_verify(&ec->publicKey, digest, digestLen, r, s); @@ -2396,22 +2245,19 @@ static int ecdsa_verifysig(void *key, const char *sig, int siglen, return ret; } -static unsigned char *ecdsa_sign(void *key, const char *data, int datalen, - int *siglen) +static void ecdsa_sign(ssh_key *key, const void *data, int datalen, + BinarySink *bs) { - struct ec_key *ec = (struct ec_key *) key; + struct ec_key *ec = container_of(key, struct ec_key, sshk); const struct ecsign_extra *extra = - (const struct ecsign_extra *)ec->signalg->extra; + (const struct ecsign_extra *)ec->sshk->extra; unsigned char digest[512 / 8]; int digestLen; Bignum r = NULL, s = NULL; - unsigned char *buf, *p; - int rlen, slen, namelen; int i; - if (!ec->privateKey || !ec->publicKey.curve) { - return NULL; - } + assert(ec->privateKey); + assert(ec->publicKey.curve); if (ec->publicKey.curve->type == EC_EDWARDS) { struct ec_point *rp; @@ -2425,15 +2271,12 @@ static unsigned char *ecdsa_sign(void *key, const char *data, int datalen, * S = (r + H(encodepoint(R) + encodepoint(pk) + m) * a) % l */ { unsigned char hash[512/8]; - unsigned char b; Bignum a; SHA512_State hs; SHA512_Init(&hs); - for (i = 0; i < pointlen; ++i) { - unsigned char b = (unsigned char)bignum_byte(ec->privateKey, i); - SHA512_Bytes(&hs, &b, 1); - } + for (i = 0; i < pointlen; ++i) + put_byte(&hs, bignum_byte(ec->privateKey, i)); SHA512_Final(&hs, hash); @@ -2447,45 +2290,34 @@ static unsigned char *ecdsa_sign(void *key, const char *data, int datalen, a = bignum_from_bytes_le(hash, 32); SHA512_Init(&hs); - SHA512_Bytes(&hs, - hash+(ec->publicKey.curve->fieldBits / 8), - (ec->publicKey.curve->fieldBits / 4) - - (ec->publicKey.curve->fieldBits / 8)); - SHA512_Bytes(&hs, data, datalen); + put_data(&hs, hash+(ec->publicKey.curve->fieldBits / 8), + ((ec->publicKey.curve->fieldBits / 4) - + (ec->publicKey.curve->fieldBits / 8))); + put_data(&hs, data, datalen); SHA512_Final(&hs, hash); r = bignum_from_bytes_le(hash, 512/8); rp = ecp_mul(&ec->publicKey.curve->e.B, r); - if (!rp) { - freebn(r); - freebn(a); - return NULL; - } + assert(rp); /* Now calculate s */ SHA512_Init(&hs); /* Encode the point R */ - for (i = 0; i < pointlen - 1; ++i) { - b = bignum_byte(rp->y, i); - SHA512_Bytes(&hs, &b, 1); - } + for (i = 0; i < pointlen - 1; ++i) + put_byte(&hs, bignum_byte(rp->y, i)); /* Unset last bit of y and set first bit of x in its place */ - b = bignum_byte(rp->y, i) & 0x7f; - b |= bignum_bit(rp->x, 0) << 7; - SHA512_Bytes(&hs, &b, 1); + put_byte(&hs, ((bignum_byte(rp->y, i) & 0x7f) | + (bignum_bit(rp->x, 0) << 7))); /* Encode the point pk */ - for (i = 0; i < pointlen - 1; ++i) { - b = bignum_byte(ec->publicKey.y, i); - SHA512_Bytes(&hs, &b, 1); - } + for (i = 0; i < pointlen - 1; ++i) + put_byte(&hs, bignum_byte(ec->publicKey.y, i)); /* Unset last bit of y and set first bit of x in its place */ - b = bignum_byte(ec->publicKey.y, i) & 0x7f; - b |= bignum_bit(ec->publicKey.x, 0) << 7; - SHA512_Bytes(&hs, &b, 1); + put_byte(&hs, ((bignum_byte(ec->publicKey.y, i) & 0x7f) | + (bignum_bit(ec->publicKey.x, 0) << 7))); /* Add the message */ - SHA512_Bytes(&hs, data, datalen); + put_data(&hs, data, datalen); SHA512_Final(&hs, hash); { @@ -2504,97 +2336,69 @@ static unsigned char *ecdsa_sign(void *key, const char *data, int datalen, } /* Format the output */ - namelen = strlen(ec->signalg->name); - *siglen = 4+namelen+4+((ec->publicKey.curve->fieldBits / 8)*2); - buf = snewn(*siglen, unsigned char); - p = buf; - PUT_32BIT(p, namelen); - p += 4; - memcpy(p, ec->signalg->name, namelen); - p += namelen; - PUT_32BIT(p, ((ec->publicKey.curve->fieldBits / 8)*2)); - p += 4; + put_stringz(bs, ec->sshk->ssh_id); + pointlen = ec->publicKey.curve->fieldBits / 8; + put_uint32(bs, pointlen * 2); /* Encode the point */ - pointlen = ec->publicKey.curve->fieldBits / 8; - for (i = 0; i < pointlen - 1; ++i) { - *p++ = bignum_byte(rp->y, i); - } + for (i = 0; i < pointlen - 1; ++i) + put_byte(bs, bignum_byte(rp->y, i)); /* Unset last bit of y and set first bit of x in its place */ - *p = bignum_byte(rp->y, i) & 0x7f; - *p++ |= bignum_bit(rp->x, 0) << 7; + put_byte(bs, ((bignum_byte(rp->y, i) & 0x7f) | + (bignum_bit(rp->x, 0) << 7))); ec_point_free(rp); /* Encode the int */ - for (i = 0; i < pointlen; ++i) { - *p++ = bignum_byte(s, i); - } + for (i = 0; i < pointlen; ++i) + put_byte(bs, bignum_byte(s, i)); freebn(s); } else { - void *hashctx; + ssh_hash *hashctx; + strbuf *substr; digestLen = extra->hash->hlen; assert(digestLen <= sizeof(digest)); - hashctx = extra->hash->init(); - extra->hash->bytes(hashctx, data, datalen); - extra->hash->final(hashctx, digest); + hashctx = ssh_hash_new(extra->hash); + put_data(hashctx, data, datalen); + ssh_hash_final(hashctx, digest); /* Do the signature */ _ecdsa_sign(ec->privateKey, ec->publicKey.curve, digest, digestLen, &r, &s); - if (!r || !s) { - if (r) freebn(r); - if (s) freebn(s); - return NULL; - } - - rlen = (bignum_bitcount(r) + 8) / 8; - slen = (bignum_bitcount(s) + 8) / 8; - - namelen = strlen(ec->signalg->name); + assert(r); + assert(s); /* Format the output */ - *siglen = 8+namelen+rlen+slen+8; - buf = snewn(*siglen, unsigned char); - p = buf; - PUT_32BIT(p, namelen); - p += 4; - memcpy(p, ec->signalg->name, namelen); - p += namelen; - PUT_32BIT(p, rlen + slen + 8); - p += 4; - PUT_32BIT(p, rlen); - p += 4; - for (i = rlen; i--;) - *p++ = bignum_byte(r, i); - PUT_32BIT(p, slen); - p += 4; - for (i = slen; i--;) - *p++ = bignum_byte(s, i); + put_stringz(bs, ec->sshk->ssh_id); + + substr = strbuf_new(); + put_mp_ssh2(substr, r); + put_mp_ssh2(substr, s); + put_stringsb(bs, substr); freebn(r); freebn(s); } - - return buf; } const struct ecsign_extra sign_extra_ed25519 = { ec_ed25519, NULL, NULL, 0, }; -const struct ssh_signkey ssh_ecdsa_ed25519 = { - ecdsa_newkey, +const ssh_keyalg ssh_ecdsa_ed25519 = { + ecdsa_new_pub, + ecdsa_new_priv, + ed25519_new_priv_openssh, + ecdsa_freekey, - ecdsa_fmtkey, + ecdsa_sign, + ecdsa_verify, ecdsa_public_blob, ecdsa_private_blob, - ecdsa_createkey, - ed25519_openssh_createkey, - ed25519_openssh_fmtkey, - 2 /* point, private exponent */, + ed25519_openssh_blob, + ecdsa_cache_str, + ecdsa_pubkey_bits, - ecdsa_verifysig, - ecdsa_sign, + "ssh-ed25519", "ssh-ed25519", &sign_extra_ed25519, @@ -2608,19 +2412,21 @@ const struct ecsign_extra sign_extra_nistp256 = { ec_p256, &ssh_sha256, nistp256_oid, lenof(nistp256_oid), }; -const struct ssh_signkey ssh_ecdsa_nistp256 = { - ecdsa_newkey, +const ssh_keyalg ssh_ecdsa_nistp256 = { + ecdsa_new_pub, + ecdsa_new_priv, + ecdsa_new_priv_openssh, + ecdsa_freekey, - ecdsa_fmtkey, + ecdsa_sign, + ecdsa_verify, ecdsa_public_blob, ecdsa_private_blob, - ecdsa_createkey, - ecdsa_openssh_createkey, - ecdsa_openssh_fmtkey, - 3 /* curve name, point, private exponent */, + ecdsa_openssh_blob, + ecdsa_cache_str, + ecdsa_pubkey_bits, - ecdsa_verifysig, - ecdsa_sign, + "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp256", &sign_extra_nistp256, @@ -2634,19 +2440,21 @@ const struct ecsign_extra sign_extra_nistp384 = { ec_p384, &ssh_sha384, nistp384_oid, lenof(nistp384_oid), }; -const struct ssh_signkey ssh_ecdsa_nistp384 = { - ecdsa_newkey, +const ssh_keyalg ssh_ecdsa_nistp384 = { + ecdsa_new_pub, + ecdsa_new_priv, + ecdsa_new_priv_openssh, + ecdsa_freekey, - ecdsa_fmtkey, + ecdsa_sign, + ecdsa_verify, ecdsa_public_blob, ecdsa_private_blob, - ecdsa_createkey, - ecdsa_openssh_createkey, - ecdsa_openssh_fmtkey, - 3 /* curve name, point, private exponent */, + ecdsa_openssh_blob, + ecdsa_cache_str, + ecdsa_pubkey_bits, - ecdsa_verifysig, - ecdsa_sign, + "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp384", &sign_extra_nistp384, @@ -2660,19 +2468,21 @@ const struct ecsign_extra sign_extra_nistp521 = { ec_p521, &ssh_sha512, nistp521_oid, lenof(nistp521_oid), }; -const struct ssh_signkey ssh_ecdsa_nistp521 = { - ecdsa_newkey, +const ssh_keyalg ssh_ecdsa_nistp521 = { + ecdsa_new_pub, + ecdsa_new_priv, + ecdsa_new_priv_openssh, + ecdsa_freekey, - ecdsa_fmtkey, + ecdsa_sign, + ecdsa_verify, ecdsa_public_blob, ecdsa_private_blob, - ecdsa_createkey, - ecdsa_openssh_createkey, - ecdsa_openssh_fmtkey, - 3 /* curve name, point, private exponent */, + ecdsa_openssh_blob, + ecdsa_cache_str, + ecdsa_pubkey_bits, - ecdsa_verifysig, - ecdsa_sign, + "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp521", &sign_extra_nistp521, @@ -2735,7 +2545,7 @@ const char *ssh_ecdhkex_curve_textname(const struct ssh_kex *kex) return curve->textname; } -void *ssh_ecdhkex_newkey(const struct ssh_kex *kex) +struct ec_key *ssh_ecdhkex_newkey(const struct ssh_kex *kex) { const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; struct ec_curve *curve; @@ -2746,7 +2556,7 @@ void *ssh_ecdhkex_newkey(const struct ssh_kex *kex) key = snew(struct ec_key); - key->signalg = NULL; + key->sshk = NULL; key->publicKey.curve = curve; if (curve->type == EC_MONTGOMERY) { @@ -2796,49 +2606,34 @@ void *ssh_ecdhkex_newkey(const struct ssh_kex *kex) return key; } -char *ssh_ecdhkex_getpublic(void *key, int *len) +void ssh_ecdhkex_getpublic(struct ec_key *ec, BinarySink *bs) { - struct ec_key *ec = (struct ec_key*)key; - char *point, *p; int i; int pointlen; pointlen = (bignum_bitcount(ec->publicKey.curve->p) + 7) / 8; if (ec->publicKey.curve->type == EC_WEIERSTRASS) { - *len = 1 + pointlen * 2; + put_byte(bs, 0x04); + for (i = pointlen; i--;) + put_byte(bs, bignum_byte(ec->publicKey.x, i)); + for (i = pointlen; i--;) + put_byte(bs, bignum_byte(ec->publicKey.y, i)); } else { - *len = pointlen; + for (i = 0; i < pointlen; ++i) + put_byte(bs, bignum_byte(ec->publicKey.x, i)); } - point = (char*)snewn(*len, char); - - p = point; - if (ec->publicKey.curve->type == EC_WEIERSTRASS) { - *p++ = 0x04; - for (i = pointlen; i--;) { - *p++ = bignum_byte(ec->publicKey.x, i); - } - for (i = pointlen; i--;) { - *p++ = bignum_byte(ec->publicKey.y, i); - } - } else { - for (i = 0; i < pointlen; ++i) { - *p++ = bignum_byte(ec->publicKey.x, i); - } - } - - return point; } -Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen) +Bignum ssh_ecdhkex_getkey(struct ec_key *ec, + const void *remoteKey, int remoteKeyLen) { - struct ec_key *ec = (struct ec_key*) key; struct ec_point remote; Bignum ret; if (ec->publicKey.curve->type == EC_WEIERSTRASS) { remote.curve = ec->publicKey.curve; - remote.infinity = 0; + remote.infinity = false; if (!decodepoint(remoteKey, remoteKeyLen, &remote)) { return NULL; } @@ -2849,8 +2644,9 @@ Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen) } remote.curve = ec->publicKey.curve; - remote.infinity = 0; - remote.x = bignum_from_bytes_le((unsigned char*)remoteKey, remoteKeyLen); + remote.infinity = false; + remote.x = bignum_from_bytes_le((const unsigned char *)remoteKey, + remoteKeyLen); remote.y = NULL; remote.z = NULL; } @@ -2861,9 +2657,9 @@ Bignum ssh_ecdhkex_getkey(void *key, char *remoteKey, int remoteKeyLen) return ret; } -void ssh_ecdhkex_freekey(void *key) +void ssh_ecdhkex_freekey(struct ec_key *key) { - ecdsa_freekey(key); + ecdsa_freekey(&key->sshk); } static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 }; @@ -2907,10 +2703,10 @@ const struct ssh_kexes ssh_ecdh_kex = { * data. */ -const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid, +const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, const struct ec_curve **curve) { - static const struct ssh_signkey *algs_with_oid[] = { + static const ssh_keyalg *algs_with_oid[] = { &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521, @@ -2918,7 +2714,7 @@ const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid, int i; for (i = 0; i < lenof(algs_with_oid); i++) { - const struct ssh_signkey *alg = 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)) { @@ -2929,7 +2725,7 @@ const struct ssh_signkey *ec_alg_by_oid(int len, const void *oid, return NULL; } -const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, +const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen) { const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra; @@ -2940,28 +2736,26 @@ const unsigned char *ec_alg_oid(const struct ssh_signkey *alg, const int ec_nist_curve_lengths[] = { 256, 384, 521 }; const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths); -int ec_nist_alg_and_curve_by_bits(int bits, - const struct ec_curve **curve, - const struct ssh_signkey **alg) +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; + default: return false; } *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); - return TRUE; + return true; } -int ec_ed_alg_and_curve_by_bits(int bits, - const struct ec_curve **curve, - const struct ssh_signkey **alg) +bool ec_ed_alg_and_curve_by_bits( + int bits, const struct ec_curve **curve, const ssh_keyalg **alg) { switch (bits) { case 256: *alg = &ssh_ecdsa_ed25519; break; - default: return FALSE; + default: return false; } *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); - return TRUE; + return true; } diff --git a/sshecccert.c b/sshecccert.c new file mode 100644 index 00000000..81ace530 --- /dev/null +++ b/sshecccert.c @@ -0,0 +1,580 @@ +/* + * RSA Certificate implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +/* ----------------------------------------------------------------------- + * Implementation of the ssh-rsa-cert-v01 key type + */ + +static void ecc_cert_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *ecc_cert_new_pub(const ssh_keyalg *self, ptrlen data) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)((ssh_keyalg*)self->extra)->extra; + BinarySource src[1]; + struct ec_cert_key *certkey; + struct ec_curve *curve; + + curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS); + + BinarySource_BARE_INIT(src, data.ptr, data.len); + ptrlen certtype = get_string(src); + if (!ptrlen_eq_string(certtype, self->ssh_id)) + return NULL; + + certkey = snew(struct ec_cert_key); + memset(certkey, 0, sizeof(struct ec_cert_key)); + certkey->sshk = self; + + certkey->certificate.ptr = snewn(data.len, char); + memcpy((void*)(certkey->certificate.ptr), data.ptr, data.len); + certkey->certificate.len = data.len; + + certkey->publicKey.curve = curve; + certkey->publicKey.infinity = false; + + if (get_err(src)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + certkey->nonce = mkstr(get_string(src)); + + ptrlen curvename = get_string(src); + if (!get_point(src, &certkey->publicKey)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + if (!ptrlen_eq_string(curvename, certkey->publicKey.curve->name) || + !certkey->publicKey.x || !certkey->publicKey.y || + bignum_cmp(certkey->publicKey.x, curve->p) >= 0 || + bignum_cmp(certkey->publicKey.y, curve->p) >= 0) + { + ecc_cert_freekey(&certkey->sshk); + certkey = NULL; + } + certkey->serial = get_uint64(src); + certkey->type = get_uint32(src); + certkey->keyid = mkstr(get_string(src)); + certkey->principals = mkstr(get_string(src)); + certkey->valid_after = get_uint64(src); + certkey->valid_before = get_uint64(src); + certkey->options = mkstr(get_string(src)); + certkey->extensions = mkstr(get_string(src)); + certkey->reserved = mkstr(get_string(src)); + + ptrlen sigkey = get_string(src); + + certkey->signature = mkstr(get_string(src)); + + if (get_err(src)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } + + if (get_err(sk)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static ssh_key *ed_cert_new_pub(const ssh_keyalg *self, ptrlen data) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)((ssh_keyalg*)self->extra)->extra; + BinarySource src[1]; + struct ec_cert_key *certkey; + struct ec_curve *curve; + + curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS); + + BinarySource_BARE_INIT(src, data.ptr, data.len); + ptrlen certtype = get_string(src); + if (!ptrlen_eq_string(certtype, self->ssh_id)) + return NULL; + + certkey = snew(struct ec_cert_key); + memset(certkey, 0, sizeof(struct ec_cert_key)); + certkey->sshk = self; + + certkey->certificate.ptr = snewn(data.len, char); + memcpy((void*)(certkey->certificate.ptr), data.ptr, data.len); + certkey->certificate.len = data.len; + + certkey->publicKey.curve = curve; + certkey->publicKey.infinity = false; + + if (get_err(src)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + certkey->nonce = mkstr(get_string(src)); + + ptrlen pkstr = get_string(src); + bool gotPoint = decodepoint_ed(pkstr.ptr, pkstr.len, &certkey->publicKey); + + if (!gotPoint || !certkey->publicKey.x || !certkey->publicKey.y || + bignum_cmp(certkey->publicKey.x, curve->p) >= 0 || + bignum_cmp(certkey->publicKey.y, curve->p) >= 0) + { + ecc_cert_freekey(&certkey->sshk); + certkey = NULL; + } + certkey->serial = get_uint64(src); + certkey->type = get_uint32(src); + certkey->keyid = mkstr(get_string(src)); + certkey->principals = mkstr(get_string(src)); + certkey->valid_after = get_uint64(src); + certkey->valid_before = get_uint64(src); + certkey->options = mkstr(get_string(src)); + certkey->extensions = mkstr(get_string(src)); + certkey->reserved = mkstr(get_string(src)); + + ptrlen sigkey = get_string(src); + + certkey->signature = mkstr(get_string(src)); + + if (get_err(src)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } + + if (get_err(sk)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static void ecc_cert_freekey(ssh_key *key) +{ + struct ec_cert_key *certkey = container_of(key, struct ec_cert_key, sshk); + + if (certkey->certificate.ptr) + sfree((void*)(certkey->certificate.ptr)); + if (certkey->nonce) + sfree(certkey->nonce); + if (certkey->publicKey.x) + freebn(certkey->publicKey.x); + if (certkey->publicKey.y) + freebn(certkey->publicKey.y); + if (certkey->publicKey.z) + freebn(certkey->publicKey.z); + if (certkey->keyid) + sfree(certkey->keyid); + if (certkey->principals) + sfree(certkey->principals); + if (certkey->options) + sfree(certkey->options); + if (certkey->extensions) + sfree(certkey->extensions); + if (certkey->reserved) + sfree(certkey->reserved); + if (certkey->sigkey) + ssh_key_free(certkey->sigkey); + if (certkey->signature) + sfree(certkey->signature); + if (certkey->privateKey) + freebn(certkey->privateKey); + + sfree(certkey); +} + +static ssh_key *ecc_cert_new_priv(const ssh_keyalg *self, + ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + struct ec_cert_key *certkey; + struct ec_point *publicKey; + + sshk = ssh_key_new_pub(self, pub); + if (!sshk) { + return NULL; + } + + certkey = container_of(sshk, struct ec_cert_key, sshk); + BinarySource_BARE_INIT(src, priv.ptr, priv.len); + + if (certkey->publicKey.curve->type != EC_WEIERSTRASS + && certkey->publicKey.curve->type != EC_EDWARDS) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + if (certkey->publicKey.curve->type == EC_EDWARDS) { + certkey->privateKey = get_mp_le(src); + } else { + certkey->privateKey = get_mp_ssh2(src); + } + if (!certkey->privateKey) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + /* Check that private key generates public key */ + publicKey = ec_public(certkey->privateKey, certkey->publicKey.curve); + + if (!publicKey || + bignum_cmp(publicKey->x, certkey->publicKey.x) || + bignum_cmp(publicKey->y, certkey->publicKey.y)) + { + ecc_cert_freekey(&certkey->sshk); + certkey = NULL; + return NULL; + } + ec_point_free(publicKey); + + if (get_err(src)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static ssh_key *ecc_cert_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)((ssh_keyalg*)self->extra)->extra; + struct ec_cert_key *certkey; + struct ec_curve *curve; + + curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS); + + certkey = snew(struct ec_cert_key); + memset(certkey, 0, sizeof(struct ec_cert_key)); + certkey->sshk = self; + + ptrlen certdata = get_string(src); + certkey->certificate.ptr = snewn(certdata.len, char); + memcpy((void*)(certkey->certificate.ptr), certdata.ptr, certdata.len); + certkey->certificate.len = certdata.len; + + certkey->privateKey = get_mp_ssh2(src); + + if (get_err(src) || !certkey->privateKey) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource cert[1]; + BinarySource_BARE_INIT(cert, certkey->certificate.ptr, certkey->certificate.len); + ptrlen certtype = get_string(cert); + + certkey->nonce = mkstr(get_string(cert)); + + ptrlen curvename = get_string(src); + bool gotPoint = get_point(cert, &certkey->publicKey); + + certkey->serial = get_uint64(cert); + certkey->type = get_uint32(cert); + certkey->keyid = mkstr(get_string(cert)); + certkey->principals = mkstr(get_string(cert)); + certkey->valid_after = get_uint64(cert); + certkey->valid_before = get_uint64(cert); + certkey->options = mkstr(get_string(cert)); + certkey->extensions = mkstr(get_string(cert)); + certkey->reserved = mkstr(get_string(cert)); + + ptrlen sigkey = get_string(cert); + + certkey->signature = mkstr(get_string(cert)); + + if (get_err(cert) || !ptrlen_eq_string(certtype, self->ssh_id) || + !ptrlen_eq_string(curvename, certkey->publicKey.curve->name) || !gotPoint || + !certkey->publicKey.x || !certkey->publicKey.y || + bignum_cmp(certkey->publicKey.x, curve->p) >= 0 || + bignum_cmp(certkey->publicKey.y, curve->p) >= 0) + { + ecc_cert_freekey(&certkey->sshk); + certkey = NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } else { + certkey->sigkey = NULL; + } + + if (get_err(sk)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static ssh_key *ed_cert_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + const struct ecsign_extra *extra = + (const struct ecsign_extra *)((ssh_keyalg*)self->extra)->extra; + struct ec_cert_key *certkey; + struct ec_curve *curve; + + curve = extra->curve(); + assert(curve->type == EC_WEIERSTRASS || curve->type == EC_EDWARDS); + + certkey = snew(struct ec_cert_key); + memset(certkey, 0, sizeof(struct ec_cert_key)); + certkey->sshk = self; + + ptrlen certdata = get_string(src); + certkey->certificate.ptr = snewn(certdata.len, char); + memcpy((void*)(certkey->certificate.ptr), certdata.ptr, certdata.len); + certkey->certificate.len = certdata.len; + + certkey->privateKey = get_mp_ssh2(src); + + if (get_err(src) || !certkey->privateKey) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource cert[1]; + BinarySource_BARE_INIT(cert, certkey->certificate.ptr, certkey->certificate.len); + ptrlen certtype = get_string(cert); + + certkey->nonce = mkstr(get_string(cert)); + + ptrlen pkstr = get_string(cert); + bool gotPoint = decodepoint_ed(pkstr.ptr, pkstr.len, &certkey->publicKey); + + certkey->serial = get_uint64(cert); + certkey->type = get_uint32(cert); + certkey->keyid = mkstr(get_string(cert)); + certkey->principals = mkstr(get_string(cert)); + certkey->valid_after = get_uint64(cert); + certkey->valid_before = get_uint64(cert); + certkey->options = mkstr(get_string(cert)); + certkey->extensions = mkstr(get_string(cert)); + certkey->reserved = mkstr(get_string(cert)); + + ptrlen sigkey = get_string(cert); + + certkey->signature = mkstr(get_string(cert)); + + if (get_err(cert) || !ptrlen_eq_string(certtype, self->ssh_id) || + !gotPoint || + !certkey->publicKey.x || !certkey->publicKey.y || + bignum_cmp(certkey->publicKey.x, curve->p) >= 0 || + bignum_cmp(certkey->publicKey.y, curve->p) >= 0) + { + ecc_cert_freekey(&certkey->sshk); + certkey = NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } else { + certkey->sigkey = NULL; + } + + if (get_err(sk)) { + ecc_cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static void ecc_cert_sign(ssh_key *key, const void* data, int datalen, + BinarySink *bs) +{ + struct ec_cert_key *certkey = container_of(key, struct ec_cert_key, sshk); + struct ec_key eckey; + + eckey.publicKey.curve = certkey->publicKey.curve; + eckey.publicKey.x = certkey->publicKey.x; + eckey.publicKey.y = certkey->publicKey.y; + eckey.publicKey.z = certkey->publicKey.z; + eckey.publicKey.infinity = certkey->publicKey.infinity; + eckey.privateKey = certkey->privateKey; + eckey.sshk = (*key)->extra; + + return ssh_key_sign(&eckey.sshk, data, datalen, bs); +} + +static bool ecc_cert_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct ec_cert_key *certkey = container_of(key, struct ec_cert_key, sshk); + struct ec_key eckey; + + eckey.publicKey.curve = certkey->publicKey.curve; + eckey.publicKey.x = certkey->publicKey.x; + eckey.publicKey.y = certkey->publicKey.y; + eckey.publicKey.z = certkey->publicKey.z; + eckey.publicKey.infinity = certkey->publicKey.infinity; + eckey.privateKey = certkey->privateKey; + eckey.sshk = (*key)->extra; + + return ssh_key_verify(&eckey.sshk, sig, data); +} + +static void ecc_cert_public_blob(ssh_key *key, BinarySink *bs) +{ + struct ec_cert_key *certkey = container_of(key, struct ec_cert_key, sshk); + + // copy the certificate + put_data(bs, certkey->certificate.ptr, certkey->certificate.len); +} + +static void ecc_cert_private_blob(ssh_key *key, BinarySink *bs) +{ + struct ec_cert_key *certkey = container_of(key, struct ec_cert_key, sshk); + + put_mp_ssh2(bs, certkey->privateKey); +} + +static void ecc_cert_openssh_blob(ssh_key* key, BinarySink *bs) +{ + // don't return anything. USed only for export, and we don't export certs +} + +// Used just for looking up host keys for now, so skip +static char * ecc_cert_cache_str(ssh_key *key) +{ + char *p = snewn(1, char); + p[0] = '\0'; + return p; +} + +static int ecc_cert_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + struct ec_cert_key *certkey; + int ret; + + sshk = ssh_key_new_pub(self, pub); + if (!sshk) + return -1; + + certkey = container_of(sshk, struct ec_cert_key, sshk); + ret = certkey->publicKey.curve->fieldBits; + ecc_cert_freekey(&certkey->sshk); + + return ret; +} + +const ssh_keyalg ssh_ecdsa_cert_nistp256 = { + ecc_cert_new_pub, + ecc_cert_new_priv, + ecc_cert_new_priv_openssh, + + ecc_cert_freekey, + ecc_cert_sign, + ecc_cert_verify, + ecc_cert_public_blob, + ecc_cert_private_blob, + ecc_cert_openssh_blob, + ecc_cert_cache_str, + + ecc_cert_pubkey_bits, + + "ecdsa-sha2-nistp256-cert-v01@openssh.com", + "ecdsa-sha2-nistp256-cert-v01", + &ssh_ecdsa_nistp256 +}; + +const ssh_keyalg ssh_ecdsa_cert_nistp384 = { + ecc_cert_new_pub, + ecc_cert_new_priv, + ecc_cert_new_priv_openssh, + + ecc_cert_freekey, + ecc_cert_sign, + ecc_cert_verify, + ecc_cert_public_blob, + ecc_cert_private_blob, + ecc_cert_openssh_blob, + ecc_cert_cache_str, + + ecc_cert_pubkey_bits, + + "ecdsa-sha2-nistp384-cert-v01@openssh.com", + "ecdsa-sha2-nistp384-cert-v01", + &ssh_ecdsa_nistp384 +}; + +const ssh_keyalg ssh_ecdsa_cert_nistp521 = { + ecc_cert_new_pub, + ecc_cert_new_priv, + ecc_cert_new_priv_openssh, + + ecc_cert_freekey, + ecc_cert_sign, + ecc_cert_verify, + ecc_cert_public_blob, + ecc_cert_private_blob, + ecc_cert_openssh_blob, + ecc_cert_cache_str, + + ecc_cert_pubkey_bits, + + "ecdsa-sha2-nistp521-cert-v01@openssh.com", + "ecdsa-sha2-nistp521-cert-v01", + &ssh_ecdsa_nistp521 +}; + +const ssh_keyalg ssh_ecdsa_cert_ed25519 = { + ed_cert_new_pub, + ecc_cert_new_priv, + ed_cert_new_priv_openssh, + + ecc_cert_freekey, + ecc_cert_sign, + ecc_cert_verify, + ecc_cert_public_blob, + ecc_cert_private_blob, + ecc_cert_openssh_blob, + ecc_cert_cache_str, + + ecc_cert_pubkey_bits, + + "ssh-ed25519-cert-v01@openssh.com", + "ssh-ed25519-cert-v01", + &ssh_ecdsa_ed25519 +}; diff --git a/sshecdsag.c b/sshecdsag.c index 83eeeb03..09d2eba3 100644 --- a/sshecdsag.c +++ b/sshecdsag.c @@ -4,16 +4,13 @@ #include "ssh.h" -/* Forward reference from sshecc.c */ -struct ec_point *ecp_mul(const struct ec_point *a, const Bignum b); - int ec_generate(struct ec_key *key, int bits, progfn_t pfn, void *pfnparam) { struct ec_point *publicKey; if (!ec_nist_alg_and_curve_by_bits(bits, &key->publicKey.curve, - &key->signalg)) + &key->sshk)) return 0; key->privateKey = bignum_random_in_range(One, key->publicKey.curve->w.n); @@ -40,7 +37,7 @@ int ec_edgenerate(struct ec_key *key, int bits, progfn_t pfn, struct ec_point *publicKey; if (!ec_ed_alg_and_curve_by_bits(bits, &key->publicKey.curve, - &key->signalg)) + &key->sshk)) return 0; { diff --git a/sshgss.h b/sshgss.h index 32ccb4db..b1c04a35 100644 --- a/sshgss.h +++ b/sshgss.h @@ -13,6 +13,8 @@ typedef enum Ssh_gss_stat { 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; @@ -26,6 +28,10 @@ typedef enum Ssh_gss_stat { 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; @@ -79,7 +85,8 @@ typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib, 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); + 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 @@ -96,7 +103,8 @@ typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib, * 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 *); + Ssh_gss_ctx *, + time_t *expiry); /* * Frees the contents of an Ssh_gss_ctx filled in by @@ -111,7 +119,15 @@ typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib, */ 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); + 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 @@ -161,6 +177,7 @@ struct ssh_gss_library { 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; @@ -183,6 +200,18 @@ struct ssh_gss_library { 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 index 4590ed7b..26d301b7 100644 --- a/sshgssc.c +++ b/sshgssc.c @@ -1,6 +1,7 @@ #include "putty.h" #include +#include #include "sshgssc.h" #include "misc.h" @@ -38,14 +39,64 @@ static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, } static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx) + 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->maj_stat = gssctx->min_stat = GSS_S_COMPLETE; gssctx->ctx = GSS_C_NO_CONTEXT; - *ctx = (Ssh_gss_ctx) gssctx; + 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; } @@ -54,11 +105,14 @@ static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, Ssh_gss_name srv_name, int to_deleg, Ssh_gss_buf *recv_tok, - Ssh_gss_buf *send_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, @@ -74,7 +128,20 @@ static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib, NULL, /* ignore mech type */ send_tok, &ret_flags, - NULL); /* ignore time_rec */ + &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; @@ -148,6 +215,7 @@ static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib, 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; @@ -175,6 +243,16 @@ static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib, 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) { @@ -192,6 +270,7 @@ void ssh_gssapi_bind_fns(struct ssh_gss_library *lib) 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; } diff --git a/sshgssc.h b/sshgssc.h index c98ee86f..07fac009 100644 --- a/sshgssc.h +++ b/sshgssc.h @@ -10,6 +10,7 @@ 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); diff --git a/sshmac.c b/sshmac.c new file mode 100644 index 00000000..3d597418 --- /dev/null +++ b/sshmac.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); + return 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 index b39dfd3e..a9058be8 100644 --- a/sshmd5.c +++ b/sshmd5.c @@ -1,3 +1,4 @@ +#include #include "ssh.h" /* @@ -14,7 +15,7 @@ #define H(x,y,z) ( (x) ^ (y) ^ (z) ) #define I(x,y,z) ( (y) ^ ( (x) | ~(z) ) ) -#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) +#define rol(x,y) ( ((x) << (y)) | (((uint32_t)x) >> (32-y)) ) #define subround(f,w,x,y,z,k,s,ti) \ w = x + rol(w + f(x,y,z) + block[k] + ti, s) @@ -27,9 +28,9 @@ static void MD5_Core_Init(MD5_Core_State * s) s->h[3] = 0x10325476; } -static void MD5_Block(MD5_Core_State * s, uint32 * block) +static void MD5_Block(MD5_Core_State *s, uint32_t *block) { - uint32 a, b, c, d; + uint32_t a, b, c, d; a = s->h[0]; b = s->h[1]; @@ -115,25 +116,30 @@ static void MD5_Block(MD5_Core_State * s, uint32 * block) #define BLKSIZE 64 +static void MD5_BinarySink_write(BinarySink *bs, const void *data, size_t len); + void MD5Init(struct MD5Context *s) { MD5_Core_Init(&s->core); s->blkused = 0; - s->lenhi = s->lenlo = 0; + s->len = 0; + BinarySink_INIT(s, MD5_BinarySink_write); } -void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len) +static void MD5_BinarySink_write(BinarySink *bs, const void *data, size_t len) { - unsigned char *q = (unsigned char *) p; - uint32 wordblock[16]; - uint32 lenw = len; + struct MD5Context *s = BinarySink_DOWNCAST(bs, struct MD5Context); + const unsigned char *q = (const unsigned char *)data; + uint32_t wordblock[16]; + uint32_t lenw = len; int i; + assert(lenw == len); + /* * Update the length field. */ - s->lenlo += lenw; - s->lenhi += (s->lenlo < lenw); + s->len += lenw; if (s->blkused + len < BLKSIZE) { /* @@ -152,10 +158,10 @@ void MD5Update(struct MD5Context *s, unsigned char const *p, unsigned len) /* Now process the block. Gather bytes little-endian into words */ for (i = 0; i < 16; i++) { wordblock[i] = - (((uint32) s->block[i * 4 + 3]) << 24) | - (((uint32) s->block[i * 4 + 2]) << 16) | - (((uint32) s->block[i * 4 + 1]) << 8) | - (((uint32) s->block[i * 4 + 0]) << 0); + (((uint32_t) s->block[i * 4 + 3]) << 24) | + (((uint32_t) s->block[i * 4 + 2]) << 16) | + (((uint32_t) s->block[i * 4 + 1]) << 8) | + (((uint32_t) s->block[i * 4 + 0]) << 0); } MD5_Block(&s->core, wordblock); s->blkused = 0; @@ -170,30 +176,22 @@ void MD5Final(unsigned char output[16], struct MD5Context *s) int i; unsigned pad; unsigned char c[64]; - uint32 lenhi, lenlo; + uint64_t len; if (s->blkused >= 56) pad = 56 + 64 - s->blkused; else pad = 56 - s->blkused; - lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); - lenlo = (s->lenlo << 3); + len = (s->len << 3); memset(c, 0, pad); c[0] = 0x80; - MD5Update(s, c, pad); + put_data(s, c, pad); - c[7] = (lenhi >> 24) & 0xFF; - c[6] = (lenhi >> 16) & 0xFF; - c[5] = (lenhi >> 8) & 0xFF; - c[4] = (lenhi >> 0) & 0xFF; - c[3] = (lenlo >> 24) & 0xFF; - c[2] = (lenlo >> 16) & 0xFF; - c[1] = (lenlo >> 8) & 0xFF; - c[0] = (lenlo >> 0) & 0xFF; + PUT_64BIT_LSB_FIRST(c, len); - MD5Update(s, c, 8); + put_data(s, c, 8); for (i = 0; i < 4; i++) { output[4 * i + 3] = (s->core.h[i] >> 24) & 0xFF; @@ -208,7 +206,7 @@ void MD5Simple(void const *p, unsigned len, unsigned char output[16]) struct MD5Context s; MD5Init(&s); - MD5Update(&s, (unsigned char const *)p, len); + put_data(&s, (unsigned char const *)p, len); MD5Final(output, &s); smemclr(&s, sizeof(s)); } @@ -221,20 +219,41 @@ void MD5Simple(void const *p, unsigned len, unsigned char output[16]) * useful elsewhere (SOCKS5 CHAP authentication uses HMAC-MD5). */ -void *hmacmd5_make_context(void *cipher_ctx) +struct hmacmd5_context { + struct MD5Context md5[3]; + ssh2_mac mac; +}; + +struct hmacmd5_context *hmacmd5_make_context(void) { - return snewn(3, struct MD5Context); + struct hmacmd5_context *ctx = snew(struct hmacmd5_context); + BinarySink_DELEGATE_INIT(&ctx->mac, &ctx->md5[2]); + return ctx; } -void hmacmd5_free_context(void *handle) +static ssh2_mac *hmacmd5_ssh2_new(const struct ssh2_macalg *alg, + ssh2_cipher *cipher) { - smemclr(handle, 3*sizeof(struct MD5Context)); - sfree(handle); + struct hmacmd5_context *ctx = hmacmd5_make_context(); + ctx->mac.vt = alg; + return &ctx->mac; } -void hmacmd5_key(void *handle, void const *keyv, int len) +void hmacmd5_free_context(struct hmacmd5_context *ctx) +{ + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void hmacmd5_ssh2_free(ssh2_mac *mac) +{ + struct hmacmd5_context *ctx = + container_of(mac, struct hmacmd5_context, mac); + hmacmd5_free_context(ctx); +} + +void hmacmd5_key(struct hmacmd5_context *ctx, void const *keyv, int len) { - struct MD5Context *keys = (struct MD5Context *)handle; unsigned char foo[64]; unsigned char const *key = (unsigned char const *)keyv; int i; @@ -242,100 +261,62 @@ void hmacmd5_key(void *handle, void const *keyv, int len) memset(foo, 0x36, 64); for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; - MD5Init(&keys[0]); - MD5Update(&keys[0], foo, 64); + MD5Init(&ctx->md5[0]); + put_data(&ctx->md5[0], foo, 64); memset(foo, 0x5C, 64); for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; - MD5Init(&keys[1]); - MD5Update(&keys[1], foo, 64); + MD5Init(&ctx->md5[1]); + put_data(&ctx->md5[1], foo, 64); smemclr(foo, 64); /* burn the evidence */ } -static void hmacmd5_key_16(void *handle, unsigned char *key) +static void hmacmd5_ssh2_setkey(ssh2_mac *mac, const void *key) { - hmacmd5_key(handle, key, 16); + struct hmacmd5_context *ctx = + container_of(mac, struct hmacmd5_context, mac); + hmacmd5_key(ctx, key, ctx->mac.vt->keylen); } -static void hmacmd5_start(void *handle) +static void hmacmd5_start(ssh2_mac *mac) { - struct MD5Context *keys = (struct MD5Context *)handle; + struct hmacmd5_context *ctx = + container_of(mac, struct hmacmd5_context, mac); - keys[2] = keys[0]; /* structure copy */ + ctx->md5[2] = ctx->md5[0]; /* structure copy */ + BinarySink_COPIED(&ctx->md5[2]); } -static void hmacmd5_bytes(void *handle, unsigned char const *blk, int len) +static void hmacmd5_genresult(ssh2_mac *mac, unsigned char *hmac) { - struct MD5Context *keys = (struct MD5Context *)handle; - MD5Update(&keys[2], blk, len); -} - -static void hmacmd5_genresult(void *handle, unsigned char *hmac) -{ - struct MD5Context *keys = (struct MD5Context *)handle; + struct hmacmd5_context *ctx = + container_of(mac, struct hmacmd5_context, mac); struct MD5Context s; unsigned char intermediate[16]; - s = keys[2]; /* structure copy */ + s = ctx->md5[2]; /* structure copy */ + BinarySink_COPIED(&s); MD5Final(intermediate, &s); - s = keys[1]; /* structure copy */ - MD5Update(&s, intermediate, 16); + s = ctx->md5[1]; /* structure copy */ + BinarySink_COPIED(&s); + put_data(&s, intermediate, 16); MD5Final(hmac, &s); + smemclr(intermediate, sizeof(intermediate)); } -static int hmacmd5_verresult(void *handle, unsigned char const *hmac) -{ - unsigned char correct[16]; - hmacmd5_genresult(handle, correct); - return smemeq(correct, hmac, 16); -} - -static void hmacmd5_do_hmac_internal(void *handle, - unsigned char const *blk, int len, - unsigned char const *blk2, int len2, - unsigned char *hmac) -{ - hmacmd5_start(handle); - hmacmd5_bytes(handle, blk, len); - if (blk2) hmacmd5_bytes(handle, blk2, len2); - hmacmd5_genresult(handle, hmac); -} - -void hmacmd5_do_hmac(void *handle, unsigned char const *blk, int len, - unsigned char *hmac) -{ - hmacmd5_do_hmac_internal(handle, blk, len, NULL, 0, hmac); -} - -static void hmacmd5_do_hmac_ssh(void *handle, unsigned char const *blk, int len, - unsigned long seq, unsigned char *hmac) -{ - unsigned char seqbuf[16]; - - PUT_32BIT_MSB_FIRST(seqbuf, seq); - hmacmd5_do_hmac_internal(handle, seqbuf, 4, blk, len, hmac); -} - -static void hmacmd5_generate(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - hmacmd5_do_hmac_ssh(handle, blk, len, seq, blk + len); -} - -static int hmacmd5_verify(void *handle, unsigned char *blk, int len, - unsigned long seq) +void hmacmd5_do_hmac(struct hmacmd5_context *ctx, + const void *blk, int len, unsigned char *hmac) { - unsigned char correct[16]; - hmacmd5_do_hmac_ssh(handle, blk, len, seq, correct); - return smemeq(correct, blk + len, 16); + ssh2_mac_start(&ctx->mac); + put_data(&ctx->mac, blk, len); + return ssh2_mac_genresult(&ctx->mac, hmac); } -const struct ssh_mac ssh_hmac_md5 = { - hmacmd5_make_context, hmacmd5_free_context, hmacmd5_key_16, - hmacmd5_generate, hmacmd5_verify, - hmacmd5_start, hmacmd5_bytes, hmacmd5_genresult, hmacmd5_verresult, +const struct ssh2_macalg ssh_hmac_md5 = { + hmacmd5_ssh2_new, hmacmd5_ssh2_free, hmacmd5_ssh2_setkey, + hmacmd5_start, hmacmd5_genresult, "hmac-md5", "hmac-md5-etm@openssh.com", 16, 16, "HMAC-MD5" diff --git a/sshppl.h b/sshppl.h new file mode 100644 index 00000000..80be9d14 --- /dev/null +++ b/sshppl.h @@ -0,0 +1,161 @@ +/* + * 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); + +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); + + /* 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; +}; + +#define ssh_ppl_process_queue(ppl) ((ppl)->vt->process_queue(ppl)) +#define ssh_ppl_get_specials(ppl, add, ctx) \ + ((ppl)->vt->get_specials(ppl, add, ctx)) +#define ssh_ppl_special_cmd(ppl, code, arg) \ + ((ppl)->vt->special_cmd(ppl, code, arg)) +#define ssh_ppl_want_user_input(ppl) ((ppl)->vt->want_user_input(ppl)) +#define ssh_ppl_got_user_input(ppl) ((ppl)->vt->got_user_input(ppl)) +#define ssh_ppl_reconfigure(ppl, conf) ((ppl)->vt->reconfigure(ppl, conf)) + +/* 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); + +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, + bool is_server); +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, 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, and takes a double pair of parens because it passes a whole + * argument list to dupprintf. */ +#define ppl_logevent(params) ( \ + logevent_and_free((ppl)->logctx, dupprintf params)) + +/* Convenience macro for protocol layers to send formatted strings to + * the terminal. Also expects 'ppl' to be in scope and takes double + * parens. */ +#define ppl_printf(params) \ + ssh_ppl_user_output_string_and_free(ppl, dupprintf params) +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, + struct RSAKey *hostkey, struct 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/sshpubk.c b/sshpubk.c index 1a27c313..ecd1704f 100644 --- a/sshpubk.c +++ b/sshpubk.c @@ -23,74 +23,56 @@ static int key_type_fp(FILE *fp); -static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only, - char **commentptr, const char *passphrase, - const char **error) +static int rsa_ssh1_load_main(FILE * fp, struct RSAKey *key, bool pub_only, + char **commentptr, const char *passphrase, + const char **error) { - unsigned char buf[16384]; - unsigned char keybuf[16]; - int len; - int i, j, ciphertype; + strbuf *buf; + int ciphertype; int ret = 0; struct MD5Context md5c; - char *comment; + ptrlen comment; + BinarySource src[1]; *error = NULL; /* Slurp the whole file (minus the header) into a buffer. */ - len = fread(buf, 1, sizeof(buf), fp); - fclose(fp); - if (len < 0 || len == sizeof(buf)) { - *error = "error reading file"; - goto end; /* file too big or not read */ + buf = strbuf_new(); + { + int ch; + while ((ch = fgetc(fp)) != EOF) + put_byte(buf, ch); } + fclose(fp); + + BinarySource_BARE_INIT(src, buf->u, buf->len); - i = 0; *error = "file format error"; /* - * A zero byte. (The signature includes a terminating NUL.) + * A zero byte. (The signature includes a terminating NUL, which + * we haven't gone past yet because we read it using fgets which + * stopped after the \n.) */ - if (len - i < 1 || buf[i] != 0) + if (get_byte(src) != 0) goto end; - i++; /* One byte giving encryption type, and one reserved uint32. */ - if (len - i < 1) - goto end; - ciphertype = buf[i]; + ciphertype = get_byte(src); if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES) goto end; - i++; - if (len - i < 4) - goto end; /* reserved field not present */ - if (buf[i] != 0 || buf[i + 1] != 0 || buf[i + 2] != 0 - || buf[i + 3] != 0) goto end; /* reserved field nonzero, panic! */ - i += 4; + if (get_uint32(src) != 0) + goto end; /* reserved field nonzero, panic! */ /* Now the serious stuff. An ordinary SSH-1 public key. */ - j = makekey(buf + i, len - i, key, NULL, 1); - if (j < 0) - goto end; /* overran */ - i += j; + get_rsa_ssh1_pub(src, key, RSA_SSH1_MODULUS_FIRST); /* Next, the comment field. */ - j = toint(GET_32BIT(buf + i)); - i += 4; - if (j < 0 || len - i < j) - goto end; - comment = snewn(j + 1, char); - if (comment) { - memcpy(comment, buf + i, j); - comment[j] = '\0'; - } - i += j; + comment = get_string(src); if (commentptr) - *commentptr = dupstr(comment); + *commentptr = mkstr(comment); if (key) - key->comment = comment; - else - sfree(comment); + key->comment = mkstr(comment); if (pub_only) { ret = 1; @@ -107,10 +89,16 @@ static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only, * Decrypt remainder of buffer. */ if (ciphertype) { + unsigned char keybuf[16]; + size_t enclen = buf->len - src->pos; + + if (enclen & 7) + goto end; + MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + put_data(&md5c, passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); - des3_decrypt_pubkey(keybuf, buf + i, (len - i + 7) & ~7); + des3_decrypt_pubkey(keybuf, buf->u + src->pos, enclen); smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ } @@ -118,32 +106,27 @@ static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only, * We are now in the secret part of the key. The first four * bytes should be of the form a, b, a, b. */ - if (len - i < 4) - goto end; - if (buf[i] != buf[i + 2] || buf[i + 1] != buf[i + 3]) { - *error = "wrong passphrase"; - ret = -1; - goto end; + { + int b0a = get_byte(src); + int b1a = get_byte(src); + int b0b = get_byte(src); + int b1b = get_byte(src); + if (b0a != b0b || b1a != b1b) { + *error = "wrong passphrase"; + ret = -1; + goto end; + } } - i += 4; /* * After that, we have one further bignum which is our * decryption exponent, and then the three auxiliary values * (iqmp, q, p). */ - j = makeprivate(buf + i, len - i, key); - if (j < 0) goto end; - i += j; - j = ssh1_read_bignum(buf + i, len - i, &key->iqmp); - if (j < 0) goto end; - i += j; - j = ssh1_read_bignum(buf + i, len - i, &key->q); - if (j < 0) goto end; - i += j; - j = ssh1_read_bignum(buf + i, len - i, &key->p); - if (j < 0) goto end; - i += j; + get_rsa_ssh1_priv(src, key); + key->iqmp = get_mp_ssh1(src); + key->q = get_mp_ssh1(src); + key->p = get_mp_ssh1(src); if (!rsa_verify(key)) { *error = "rsa_verify failed"; @@ -153,19 +136,19 @@ static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only, ret = 1; end: - smemclr(buf, sizeof(buf)); /* burn the evidence */ + strbuf_free(buf); return ret; } -int loadrsakey(const Filename *filename, struct RSAKey *key, - const char *passphrase, const char **errorstr) +int rsa_ssh1_loadkey(const Filename *filename, struct RSAKey *key, + const char *passphrase, const char **errorstr) { FILE *fp; char buf[64]; int ret = 0; const char *error = NULL; - fp = f_open(filename, "rb", FALSE); + fp = f_open(filename, "rb", false); if (!fp) { error = "can't open file"; goto end; @@ -179,7 +162,7 @@ int loadrsakey(const Filename *filename, struct RSAKey *key, /* * This routine will take care of calling fclose() for us. */ - ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error); + ret = rsa_ssh1_load_main(fp, key, false, NULL, passphrase, &error); fp = NULL; goto end; } @@ -201,14 +184,14 @@ int loadrsakey(const Filename *filename, struct RSAKey *key, * See whether an RSA key is encrypted. Return its comment field as * well. */ -int rsakey_encrypted(const Filename *filename, char **comment) +bool rsa_ssh1_encrypted(const Filename *filename, char **comment) { FILE *fp; char buf[64]; - fp = f_open(filename, "rb", FALSE); + fp = f_open(filename, "rb", false); if (!fp) - return 0; /* doesn't even exist */ + return false; /* doesn't even exist */ /* * Read the first line of the file and see if it's a v1 private @@ -219,19 +202,18 @@ int rsakey_encrypted(const Filename *filename, char **comment) /* * This routine will take care of calling fclose() for us. */ - return loadrsakey_main(fp, NULL, FALSE, comment, NULL, &dummy); + return rsa_ssh1_load_main(fp, NULL, false, comment, NULL, &dummy) == 1; } fclose(fp); - return 0; /* wasn't the right kind of file */ + return false; /* wasn't the right kind of file */ } /* - * Return a malloc'ed chunk of memory containing the public blob of - * an RSA key, as given in the agent protocol (modulus bits, - * exponent, modulus). + * Read the public part of an SSH-1 RSA key from a file (public or + * private), and generate its public blob in exponent-first order. */ -int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, - char **commentptr, const char **errorstr) +int rsa_ssh1_loadpub(const Filename *filename, BinarySink *bs, + char **commentptr, const char **errorstr) { FILE *fp; char buf[64]; @@ -240,11 +222,9 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, const char *error = NULL; /* Default return if we fail. */ - *blob = NULL; - *bloblen = 0; ret = 0; - fp = f_open(filename, "rb", FALSE); + fp = f_open(filename, "rb", false); if (!fp) { error = "can't open file"; goto end; @@ -256,12 +236,12 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, */ if (fgets(buf, sizeof(buf), fp) && !strcmp(buf, rsa_signature)) { memset(&key, 0, sizeof(key)); - if (loadrsakey_main(fp, &key, TRUE, commentptr, NULL, &error)) { - *blob = rsa_public_blob(&key, bloblen); + if (rsa_ssh1_load_main(fp, &key, true, commentptr, NULL, &error)) { + rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST); freersakey(&key); ret = 1; } - fp = NULL; /* loadrsakey_main unconditionally closes fp */ + fp = NULL; /* rsa_ssh1_load_main unconditionally closes fp */ } else { /* * Try interpreting the file as an SSH-1 public key. @@ -307,7 +287,7 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, } if (commentptr) *commentptr = commentp ? dupstr(commentp) : NULL; - *blob = rsa_public_blob(&key, bloblen); + rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST); freersakey(&key); sfree(line); fclose(fp); @@ -327,106 +307,82 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen, } /* - * Save an RSA key file. Return nonzero on success. + * Save an RSA key file. Return true on success. */ -int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase) +bool rsa_ssh1_savekey(const Filename *filename, struct RSAKey *key, + char *passphrase) { - unsigned char buf[16384]; - unsigned char keybuf[16]; - struct MD5Context md5c; - unsigned char *p, *estart; + strbuf *buf = strbuf_new(); + int estart; FILE *fp; /* - * Write the initial signature. - */ - p = buf; - memcpy(p, rsa_signature, sizeof(rsa_signature)); - p += sizeof(rsa_signature); - - /* - * One byte giving encryption type, and one reserved (zero) - * uint32. + * The public part of the key. */ - *p++ = (passphrase ? SSH_CIPHER_3DES : 0); - PUT_32BIT(p, 0); - p += 4; - - /* - * An ordinary SSH-1 public key consists of: a uint32 - * containing the bit count, then two bignums containing the - * modulus and exponent respectively. - */ - PUT_32BIT(p, bignum_bitcount(key->modulus)); - p += 4; - p += ssh1_write_bignum(p, key->modulus); - p += ssh1_write_bignum(p, key->exponent); - - /* - * A string containing the comment field. - */ - if (key->comment) { - PUT_32BIT(p, strlen(key->comment)); - p += 4; - memcpy(p, key->comment, strlen(key->comment)); - p += strlen(key->comment); - } else { - PUT_32BIT(p, 0); - p += 4; - } + put_data(buf, rsa_signature, sizeof(rsa_signature)); + put_byte(buf, passphrase ? SSH_CIPHER_3DES : 0); /* encryption type */ + put_uint32(buf, 0); /* reserved */ + rsa_ssh1_public_blob(BinarySink_UPCAST(buf), key, + RSA_SSH1_MODULUS_FIRST); + put_stringz(buf, NULLTOEMPTY(key->comment)); /* * The encrypted portion starts here. */ - estart = p; + estart = buf->len; /* * Two bytes, then the same two bytes repeated. */ - *p++ = random_byte(); - *p++ = random_byte(); - p[0] = p[-2]; - p[1] = p[-1]; - p += 2; + { + unsigned char b0 = random_byte(); + unsigned char b1 = random_byte(); + put_byte(buf, b0); + put_byte(buf, b1); + put_byte(buf, b0); + put_byte(buf, b1); + } /* * Four more bignums: the decryption exponent, then iqmp, then * q, then p. */ - p += ssh1_write_bignum(p, key->private_exponent); - p += ssh1_write_bignum(p, key->iqmp); - p += ssh1_write_bignum(p, key->q); - p += ssh1_write_bignum(p, key->p); + put_mp_ssh1(buf, key->private_exponent); + put_mp_ssh1(buf, key->iqmp); + put_mp_ssh1(buf, key->q); + put_mp_ssh1(buf, key->p); /* * Now write zeros until the encrypted portion is a multiple of * 8 bytes. */ - while ((p - estart) % 8) - *p++ = '\0'; + put_padding(buf, (estart - buf->len) & 7, 0); /* * Now encrypt the encrypted portion. */ if (passphrase) { + struct MD5Context md5c; + unsigned char keybuf[16]; + MD5Init(&md5c); - MD5Update(&md5c, (unsigned char *)passphrase, strlen(passphrase)); + put_data(&md5c, passphrase, strlen(passphrase)); MD5Final(keybuf, &md5c); - des3_encrypt_pubkey(keybuf, estart, p - estart); + des3_encrypt_pubkey(keybuf, buf->u + estart, buf->len - estart); smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ } /* * Done. Write the result to the file. */ - fp = f_open(filename, "wb", TRUE); + fp = f_open(filename, "wb", true); if (fp) { - int ret = (fwrite(buf, 1, p - buf, fp) == (size_t) (p - buf)); + bool ret = (fwrite(buf->u, 1, buf->len, fp) == (size_t) (buf->len)); if (fclose(fp)) - ret = 0; + ret = false; return ret; } else - return 0; + return false; } /* ---------------------------------------------------------------------- @@ -512,7 +468,7 @@ int saversakey(const Filename *filename, struct RSAKey *key, char *passphrase) * an HMAC (this was generated for unencrypted keys). */ -static int read_header(FILE * fp, char *header) +static bool read_header(FILE * fp, char *header) { int len = 39; int c; @@ -520,20 +476,20 @@ static int read_header(FILE * fp, char *header) while (1) { c = fgetc(fp); if (c == '\n' || c == '\r' || c == EOF) - return 0; /* failure */ + return false; /* failure */ if (c == ':') { c = fgetc(fp); if (c != ' ') - return 0; + return false; *header = '\0'; - return 1; /* success! */ + return true; /* success! */ } if (len == 0) - return 0; /* failure */ + return false; /* failure */ *header++ = c; len--; } - return 0; /* failure */ + return false; /* failure */ } static char *read_body(FILE * fp) @@ -567,71 +523,80 @@ static char *read_body(FILE * fp) } } -static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen) +static bool read_blob(FILE *fp, int nlines, BinarySink *bs) { unsigned char *blob; char *line; - int linelen, len; + int linelen; int i, j, k; /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */ blob = snewn(48 * nlines, unsigned char); - len = 0; for (i = 0; i < nlines; i++) { line = read_body(fp); if (!line) { sfree(blob); - return NULL; + return false; } linelen = strlen(line); if (linelen % 4 != 0 || linelen > 64) { sfree(blob); sfree(line); - return NULL; + return false; } for (j = 0; j < linelen; j += 4) { - k = base64_decode_atom(line + j, blob + len); + unsigned char decoded[3]; + k = base64_decode_atom(line + j, decoded); if (!k) { sfree(line); sfree(blob); - return NULL; + return false; } - len += k; + put_data(bs, decoded, k); } sfree(line); } - *bloblen = len; - return blob; + return true; } /* * Magic error return value for when the passphrase is wrong. */ -struct ssh2_userkey ssh2_wrong_passphrase = { - NULL, NULL, NULL -}; +struct ssh2_userkey ssh2_wrong_passphrase = { NULL, NULL }; -const struct ssh_signkey *find_pubkey_alg_len(int namelen, const char *name) +const ssh_keyalg *find_pubkey_alg_len(ptrlen name) { - if (match_ssh_id(namelen, name, "ssh-rsa")) + if (ptrlen_eq_string(name, "ssh-rsa")) return &ssh_rsa; - else if (match_ssh_id(namelen, name, "ssh-dss")) + else if (ptrlen_eq_string(name, "ssh-dss")) return &ssh_dss; - else if (match_ssh_id(namelen, name, "ecdsa-sha2-nistp256")) + else if (ptrlen_eq_string(name, "ecdsa-sha2-nistp256")) return &ssh_ecdsa_nistp256; - else if (match_ssh_id(namelen, name, "ecdsa-sha2-nistp384")) + else if (ptrlen_eq_string(name, "ecdsa-sha2-nistp384")) return &ssh_ecdsa_nistp384; - else if (match_ssh_id(namelen, name, "ecdsa-sha2-nistp521")) + else if (ptrlen_eq_string(name, "ecdsa-sha2-nistp521")) return &ssh_ecdsa_nistp521; - else if (match_ssh_id(namelen, name, "ssh-ed25519")) + else if (ptrlen_eq_string(name, "ssh-ed25519")) return &ssh_ecdsa_ed25519; + else if (ptrlen_eq_string(name, "ssh-rsa-cert-v01@openssh.com")) + return &ssh_cert_rsa; + else if (ptrlen_eq_string(name, "ssh-dss-cert-v01@openssh.com")) + return &ssh_cert_dss; + else if (ptrlen_eq_string(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com")) + return &ssh_ecdsa_cert_nistp256; + else if (ptrlen_eq_string(name, "ecdsa-sha2-nistp384-cert-v01@openssh.com")) + return &ssh_ecdsa_cert_nistp384; + else if (ptrlen_eq_string(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com")) + return &ssh_ecdsa_cert_nistp521; + else if (ptrlen_eq_string(name, "ssh-ed25519-cert-v01@openssh.com")) + return &ssh_ecdsa_cert_ed25519; else return NULL; } -const struct ssh_signkey *find_pubkey_alg(const char *name) +const ssh_keyalg *find_pubkey_alg(const char *name) { - return find_pubkey_alg_len(strlen(name), name); + return find_pubkey_alg_len(ptrlen_from_asciz(name)); } struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, @@ -640,12 +605,12 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, { FILE *fp; char header[40], *b, *encryption, *comment, *mac; - const struct ssh_signkey *alg; + const ssh_keyalg *alg; struct ssh2_userkey *ret; int cipher, cipherblk; - unsigned char *public_blob, *private_blob; - int public_blob_len, private_blob_len; - int i, is_mac, old_fmt; + strbuf *public_blob, *private_blob; + int i; + bool is_mac, old_fmt; int passlen = passphrase ? strlen(passphrase) : 0; const char *error = NULL; @@ -653,21 +618,23 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, encryption = comment = mac = NULL; public_blob = private_blob = NULL; - fp = f_open(filename, "rb", FALSE); + fp = f_open(filename, "rb", false); if (!fp) { error = "can't open file"; goto error; } /* Read the first header line which contains the key type. */ - if (!read_header(fp, header)) + if (!read_header(fp, header)) { + error = "no header line found in key file"; goto error; + } if (0 == strcmp(header, "PuTTY-User-Key-File-2")) { - old_fmt = 0; + old_fmt = false; } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) { /* this is an old key file; warn and then continue */ old_keyfile_warning(); - old_fmt = 1; + old_fmt = true; } else if (0 == strncmp(header, "PuTTY-User-Key-File-", 20)) { /* this is a key file FROM THE FUTURE; refuse it, but with a * more specific error message than the generic one below */ @@ -716,7 +683,8 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, goto error; i = atoi(b); sfree(b); - if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) + public_blob = strbuf_new(); + if (!read_blob(fp, i, BinarySink_UPCAST(public_blob))) goto error; /* Read the Private-Lines header line and the Private blob. */ @@ -726,7 +694,8 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, goto error; i = atoi(b); sfree(b); - if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL) + private_blob = strbuf_new(); + if (!read_blob(fp, i, BinarySink_UPCAST(private_blob))) goto error; /* Read the Private-MAC or Private-Hash header line. */ @@ -735,11 +704,11 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, if (0 == strcmp(header, "Private-MAC")) { if ((mac = read_body(fp)) == NULL) goto error; - is_mac = 1; + is_mac = true; } else if (0 == strcmp(header, "Private-Hash") && old_fmt) { if ((mac = read_body(fp)) == NULL) goto error; - is_mac = 0; + is_mac = false; } else goto error; @@ -755,18 +724,18 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, if (!passphrase) goto error; - if (private_blob_len % cipherblk) + if (private_blob->len % cipherblk) goto error; SHA_Init(&s); - SHA_Bytes(&s, "\0\0\0\0", 4); - SHA_Bytes(&s, passphrase, passlen); + put_uint32(&s, 0); + put_data(&s, passphrase, passlen); SHA_Final(&s, key + 0); SHA_Init(&s); - SHA_Bytes(&s, "\0\0\0\1", 4); - SHA_Bytes(&s, passphrase, passlen); + put_uint32(&s, 1); + put_data(&s, passphrase, passlen); SHA_Final(&s, key + 20); - aes256_decrypt_pubkey(key, private_blob, private_blob_len); + aes256_decrypt_pubkey(key, private_blob->u, private_blob->len); } /* @@ -775,35 +744,23 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, { char realmac[41]; unsigned char binary[20]; - unsigned char *macdata; - int maclen; - int free_macdata; + strbuf *macdata; + bool free_macdata; if (old_fmt) { /* MAC (or hash) only covers the private blob. */ macdata = private_blob; - maclen = private_blob_len; - free_macdata = 0; + free_macdata = false; } else { - unsigned char *p; - int namelen = strlen(alg->name); - int enclen = strlen(encryption); - int commlen = strlen(comment); - maclen = (4 + namelen + - 4 + enclen + - 4 + commlen + - 4 + public_blob_len + - 4 + private_blob_len); - macdata = snewn(maclen, unsigned char); - p = macdata; -#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) - DO_STR(alg->name, namelen); - DO_STR(encryption, enclen); - DO_STR(comment, commlen); - DO_STR(public_blob, public_blob_len); - DO_STR(private_blob, private_blob_len); - - free_macdata = 1; + macdata = strbuf_new(); + put_stringz(macdata, alg->ssh_id); + put_stringz(macdata, encryption); + put_stringz(macdata, comment); + put_string(macdata, public_blob->s, + public_blob->len); + put_string(macdata, private_blob->s, + private_blob->len); + free_macdata = true; } if (is_mac) { @@ -812,23 +769,22 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, char header[] = "putty-private-key-file-mac-key"; SHA_Init(&s); - SHA_Bytes(&s, header, sizeof(header)-1); + put_data(&s, header, sizeof(header)-1); if (cipher && passphrase) - SHA_Bytes(&s, passphrase, passlen); + put_data(&s, passphrase, passlen); SHA_Final(&s, mackey); - hmac_sha1_simple(mackey, 20, macdata, maclen, binary); + hmac_sha1_simple(mackey, 20, macdata->s, + macdata->len, binary); smemclr(mackey, sizeof(mackey)); smemclr(&s, sizeof(s)); } else { - SHA_Simple(macdata, maclen, binary); + SHA_Simple(macdata->s, macdata->len, binary); } - if (free_macdata) { - smemclr(macdata, maclen); - sfree(macdata); - } + if (free_macdata) + strbuf_free(macdata); for (i = 0; i < 20; i++) sprintf(realmac + 2 * i, "%02x", binary[i]); @@ -853,19 +809,18 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, * Create and return the key. */ ret = snew(struct ssh2_userkey); - ret->alg = alg; ret->comment = comment; - ret->data = alg->createkey(alg, public_blob, public_blob_len, - private_blob, private_blob_len); - if (!ret->data) { + ret->key = ssh_key_new_priv( + alg, ptrlen_from_strbuf(public_blob), + ptrlen_from_strbuf(private_blob)); + if (!ret->key) { sfree(ret); ret = NULL; error = "createkey failed"; goto error; } - sfree(public_blob); - smemclr(private_blob, private_blob_len); - sfree(private_blob); + strbuf_free(public_blob); + strbuf_free(private_blob); sfree(encryption); if (errorstr) *errorstr = NULL; @@ -884,19 +839,17 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename, if (mac) sfree(mac); if (public_blob) - sfree(public_blob); - if (private_blob) { - smemclr(private_blob, private_blob_len); - sfree(private_blob); - } + strbuf_free(public_blob); + if (private_blob) + strbuf_free(private_blob); if (errorstr) *errorstr = error; return ret; } -unsigned char *rfc4716_loadpub(FILE *fp, char **algorithm, - int *pub_blob_len, char **commentptr, - const char **errorstr) +bool rfc4716_loadpub(FILE *fp, char **algorithm, + BinarySink *bs, + char **commentptr, const char **errorstr) { const char *error; char *line, *colon, *value; @@ -1011,13 +964,13 @@ unsigned char *rfc4716_loadpub(FILE *fp, char **algorithm, } if (algorithm) *algorithm = dupprintf("%.*s", alglen, pubblob+4); - if (pub_blob_len) - *pub_blob_len = pubbloblen; if (commentptr) *commentptr = comment; else sfree(comment); - return pubblob; + put_data(bs, pubblob, pubbloblen); + sfree(pubblob); + return true; error: sfree(line); @@ -1025,12 +978,12 @@ unsigned char *rfc4716_loadpub(FILE *fp, char **algorithm, sfree(pubblob); if (errorstr) *errorstr = error; - return NULL; + return false; } -unsigned char *openssh_loadpub(FILE *fp, char **algorithm, - int *pub_blob_len, char **commentptr, - const char **errorstr) +bool openssh_loadpub(FILE *fp, char **algorithm, + BinarySink *bs, + char **commentptr, const char **errorstr) { const char *error; char *line, *base64; @@ -1086,14 +1039,14 @@ unsigned char *openssh_loadpub(FILE *fp, char **algorithm, */ if (algorithm) *algorithm = dupstr(line); - if (pub_blob_len) - *pub_blob_len = pubbloblen; if (commentptr) *commentptr = comment; else sfree(comment); sfree(line); - return pubblob; + put_data(bs, pubblob, pubbloblen); + sfree(pubblob); + return true; error: sfree(line); @@ -1101,25 +1054,21 @@ unsigned char *openssh_loadpub(FILE *fp, char **algorithm, sfree(pubblob); if (errorstr) *errorstr = error; - return NULL; + return false; } -unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, - int *pub_blob_len, char **commentptr, - const char **errorstr) +bool ssh2_userkey_loadpub(const Filename *filename, char **algorithm, + BinarySink *bs, + char **commentptr, const char **errorstr) { FILE *fp; char header[40], *b; - const struct ssh_signkey *alg; - unsigned char *public_blob; - int public_blob_len; + const ssh_keyalg *alg; int type, i; const char *error = NULL; char *comment = NULL; - public_blob = NULL; - - fp = f_open(filename, "rb", FALSE); + fp = f_open(filename, "rb", false); if (!fp) { error = "can't open file"; goto error; @@ -1129,13 +1078,11 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, * we'll be asked to read a public blob from one of those. */ type = key_type_fp(fp); if (type == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) { - unsigned char *ret = rfc4716_loadpub(fp, algorithm, pub_blob_len, - commentptr, errorstr); + bool ret = rfc4716_loadpub(fp, algorithm, bs, commentptr, errorstr); fclose(fp); return ret; } else if (type == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - unsigned char *ret = openssh_loadpub(fp, algorithm, pub_blob_len, - commentptr, errorstr); + bool ret = openssh_loadpub(fp, algorithm, bs, commentptr, errorstr); fclose(fp); return ret; } else if (type != SSH_KEYTYPE_SSH2) { @@ -1188,15 +1135,13 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, goto error; i = atoi(b); sfree(b); - if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL) + if (!read_blob(fp, i, bs)) goto error; fclose(fp); - if (pub_blob_len) - *pub_blob_len = public_blob_len; if (algorithm) - *algorithm = dupstr(alg->name); - return public_blob; + *algorithm = dupstr(alg->ssh_id); + return true; /* * Error processing. @@ -1204,60 +1149,58 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm, error: if (fp) fclose(fp); - if (public_blob) - sfree(public_blob); if (errorstr) *errorstr = error; if (comment && commentptr) { sfree(comment); *commentptr = NULL; } - return NULL; + return false; } -int ssh2_userkey_encrypted(const Filename *filename, char **commentptr) +bool ssh2_userkey_encrypted(const Filename *filename, char **commentptr) { FILE *fp; char header[40], *b, *comment; - int ret; + bool ret; if (commentptr) *commentptr = NULL; - fp = f_open(filename, "rb", FALSE); + fp = f_open(filename, "rb", false); if (!fp) - return 0; + return false; if (!read_header(fp, header) || (0 != strcmp(header, "PuTTY-User-Key-File-2") && 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { fclose(fp); - return 0; + return false; } if ((b = read_body(fp)) == NULL) { fclose(fp); - return 0; + return false; } sfree(b); /* we don't care about key type here */ /* Read the Encryption header line. */ if (!read_header(fp, header) || 0 != strcmp(header, "Encryption")) { fclose(fp); - return 0; + return false; } if ((b = read_body(fp)) == NULL) { fclose(fp); - return 0; + return false; } /* Read the Comment header line. */ if (!read_header(fp, header) || 0 != strcmp(header, "Comment")) { fclose(fp); sfree(b); - return 1; + return true; } if ((comment = read_body(fp)) == NULL) { fclose(fp); sfree(b); - return 1; + return true; } if (commentptr) @@ -1267,9 +1210,9 @@ int ssh2_userkey_encrypted(const Filename *filename, char **commentptr) fclose(fp); if (!strcmp(b, "aes256-cbc")) - ret = 1; + ret = true; else - ret = 0; + ret = false; sfree(b); return ret; } @@ -1303,12 +1246,13 @@ void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl) fputc('\n', fp); } -int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, - char *passphrase) +bool ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, + char *passphrase) { FILE *fp; - unsigned char *pub_blob, *priv_blob, *priv_blob_encrypted; - int pub_blob_len, priv_blob_len, priv_encrypted_len; + strbuf *pub_blob, *priv_blob; + unsigned char *priv_blob_encrypted; + int priv_encrypted_len; int passlen; int cipherblk; int i; @@ -1318,13 +1262,10 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, /* * Fetch the key component blobs. */ - pub_blob = key->alg->public_blob(key->data, &pub_blob_len); - priv_blob = key->alg->private_blob(key->data, &priv_blob_len); - if (!pub_blob || !priv_blob) { - sfree(pub_blob); - sfree(priv_blob); - return 0; - } + pub_blob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pub_blob)); + priv_blob = strbuf_new(); + ssh_key_private_blob(key->key, BinarySink_UPCAST(priv_blob)); /* * Determine encryption details, and encrypt the private blob. @@ -1336,52 +1277,40 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, cipherstr = "none"; cipherblk = 1; } - priv_encrypted_len = priv_blob_len + cipherblk - 1; + priv_encrypted_len = priv_blob->len + cipherblk - 1; priv_encrypted_len -= priv_encrypted_len % cipherblk; priv_blob_encrypted = snewn(priv_encrypted_len, unsigned char); memset(priv_blob_encrypted, 0, priv_encrypted_len); - memcpy(priv_blob_encrypted, priv_blob, priv_blob_len); + memcpy(priv_blob_encrypted, priv_blob->u, priv_blob->len); /* Create padding based on the SHA hash of the unpadded blob. This prevents * too easy a known-plaintext attack on the last block. */ - SHA_Simple(priv_blob, priv_blob_len, priv_mac); - assert(priv_encrypted_len - priv_blob_len < 20); - memcpy(priv_blob_encrypted + priv_blob_len, priv_mac, - priv_encrypted_len - priv_blob_len); + SHA_Simple(priv_blob->u, priv_blob->len, priv_mac); + assert(priv_encrypted_len - priv_blob->len < 20); + memcpy(priv_blob_encrypted + priv_blob->len, priv_mac, + priv_encrypted_len - priv_blob->len); /* Now create the MAC. */ { - unsigned char *macdata; - int maclen; - unsigned char *p; - int namelen = strlen(key->alg->name); - int enclen = strlen(cipherstr); - int commlen = strlen(key->comment); + strbuf *macdata; SHA_State s; unsigned char mackey[20]; char header[] = "putty-private-key-file-mac-key"; - maclen = (4 + namelen + - 4 + enclen + - 4 + commlen + - 4 + pub_blob_len + - 4 + priv_encrypted_len); - macdata = snewn(maclen, unsigned char); - p = macdata; -#define DO_STR(s,len) PUT_32BIT(p,(len));memcpy(p+4,(s),(len));p+=4+(len) - DO_STR(key->alg->name, namelen); - DO_STR(cipherstr, enclen); - DO_STR(key->comment, commlen); - DO_STR(pub_blob, pub_blob_len); - DO_STR(priv_blob_encrypted, priv_encrypted_len); + macdata = strbuf_new(); + put_stringz(macdata, ssh_key_ssh_id(key->key)); + put_stringz(macdata, cipherstr); + put_stringz(macdata, key->comment); + put_string(macdata, pub_blob->s, pub_blob->len); + put_string(macdata, priv_blob_encrypted, priv_encrypted_len); SHA_Init(&s); - SHA_Bytes(&s, header, sizeof(header)-1); + put_data(&s, header, sizeof(header)-1); if (passphrase) - SHA_Bytes(&s, passphrase, strlen(passphrase)); + put_data(&s, passphrase, strlen(passphrase)); SHA_Final(&s, mackey); - hmac_sha1_simple(mackey, 20, macdata, maclen, priv_mac); - smemclr(macdata, maclen); - sfree(macdata); + hmac_sha1_simple(mackey, 20, macdata->s, + macdata->len, priv_mac); + strbuf_free(macdata); smemclr(mackey, sizeof(mackey)); smemclr(&s, sizeof(s)); } @@ -1393,12 +1322,12 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, passlen = strlen(passphrase); SHA_Init(&s); - SHA_Bytes(&s, "\0\0\0\0", 4); - SHA_Bytes(&s, passphrase, passlen); + put_uint32(&s, 0); + put_data(&s, passphrase, passlen); SHA_Final(&s, key + 0); SHA_Init(&s); - SHA_Bytes(&s, "\0\0\0\1", 4); - SHA_Bytes(&s, passphrase, passlen); + put_uint32(&s, 1); + put_data(&s, passphrase, passlen); SHA_Final(&s, key + 20); aes256_encrypt_pubkey(key, priv_blob_encrypted, priv_encrypted_len); @@ -1407,20 +1336,19 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, smemclr(&s, sizeof(s)); } - fp = f_open(filename, "w", TRUE); + fp = f_open(filename, "w", true); if (!fp) { - sfree(pub_blob); - smemclr(priv_blob, priv_blob_len); - sfree(priv_blob); - smemclr(priv_blob_encrypted, priv_blob_len); + strbuf_free(pub_blob); + strbuf_free(priv_blob); + smemclr(priv_blob_encrypted, priv_encrypted_len); sfree(priv_blob_encrypted); - return 0; + return false; } - fprintf(fp, "PuTTY-User-Key-File-2: %s\n", key->alg->name); + fprintf(fp, "PuTTY-User-Key-File-2: %s\n", ssh_key_ssh_id(key->key)); fprintf(fp, "Encryption: %s\n", cipherstr); fprintf(fp, "Comment: %s\n", key->comment); - fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob_len)); - base64_encode(fp, pub_blob, pub_blob_len, 64); + fprintf(fp, "Public-Lines: %d\n", base64_lines(pub_blob->len)); + base64_encode(fp, pub_blob->u, pub_blob->len, 64); fprintf(fp, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); base64_encode(fp, priv_blob_encrypted, priv_encrypted_len, 64); fprintf(fp, "Private-MAC: "); @@ -1429,12 +1357,11 @@ int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key, fprintf(fp, "\n"); fclose(fp); - sfree(pub_blob); - smemclr(priv_blob, priv_blob_len); - sfree(priv_blob); - smemclr(priv_blob_encrypted, priv_blob_len); + strbuf_free(pub_blob); + strbuf_free(priv_blob); + smemclr(priv_blob_encrypted, priv_encrypted_len); sfree(priv_blob_encrypted); - return 1; + return true; } /* ---------------------------------------------------------------------- @@ -1468,31 +1395,25 @@ static char *ssh2_pubkey_openssh_str_internal(const char *comment, int pub_len) { const unsigned char *ssh2blob = (const unsigned char *)v_pub_blob; - const char *alg; - int alglen; + ptrlen alg; char *buffer, *p; int i; - if (pub_len < 4) { - alg = NULL; - } else { - alglen = GET_32BIT(ssh2blob); - if (alglen > 0 && alglen < pub_len - 4) { - alg = (const char *)ssh2blob + 4; - } else { - alg = NULL; + { + BinarySource src[1]; + BinarySource_BARE_INIT(src, ssh2blob, pub_len); + alg = get_string(src); + if (get_err(src)) { + const char *replacement_str = "INVALID-ALGORITHM"; + alg.ptr = replacement_str; + alg.len = strlen(replacement_str); } } - if (!alg) { - alg = "INVALID-ALGORITHM"; - alglen = strlen(alg); - } - - buffer = snewn(alglen + + buffer = snewn(alg.len + 4 * ((pub_len+2) / 3) + (comment ? strlen(comment) : 0) + 3, char); - p = buffer + sprintf(buffer, "%.*s ", alglen, alg); + p = buffer + sprintf(buffer, "%.*s ", PTRLEN_PRINTF(alg)); i = 0; while (i < pub_len) { int n = (pub_len - i < 3 ? pub_len - i : 3); @@ -1500,7 +1421,7 @@ static char *ssh2_pubkey_openssh_str_internal(const char *comment, i += n; p += 4; } - if (*comment) { + if (comment) { *p++ = ' '; strcpy(p, comment); } else @@ -1511,13 +1432,14 @@ static char *ssh2_pubkey_openssh_str_internal(const char *comment, char *ssh2_pubkey_openssh_str(struct ssh2_userkey *key) { - int bloblen; - unsigned char *blob; + strbuf *blob; char *ret; - blob = key->alg->public_blob(key->data, &bloblen); - ret = ssh2_pubkey_openssh_str_internal(key->comment, blob, bloblen); - sfree(blob); + blob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(blob)); + ret = ssh2_pubkey_openssh_str_internal( + key->comment, blob->s, blob->len); + strbuf_free(blob); return ret; } @@ -1579,10 +1501,10 @@ char *ssh2_fingerprint_blob(const void *blob, int bloblen) { unsigned char digest[16]; char fingerprint_str[16*3]; - const char *algstr; - int alglen; - const struct ssh_signkey *alg; + ptrlen algname; + const ssh_keyalg *alg; int i; + BinarySource src[1]; /* * The fingerprint hash itself is always just the MD5 of the blob. @@ -1594,21 +1516,17 @@ char *ssh2_fingerprint_blob(const void *blob, int bloblen) /* * Identify the key algorithm, if possible. */ - alglen = toint(GET_32BIT((const unsigned char *)blob)); - if (alglen > 0 && alglen < bloblen-4) { - algstr = (const char *)blob + 4; - - /* - * If we can actually identify the algorithm as one we know - * about, get hold of the key's bit count too. - */ - alg = find_pubkey_alg_len(alglen, algstr); + BinarySource_BARE_INIT(src, blob, bloblen); + algname = get_string(src); + if (!get_err(src)) { + alg = find_pubkey_alg_len(algname); if (alg) { - int bits = alg->pubkey_bits(alg, blob, bloblen); - return dupprintf("%.*s %d %s", alglen, algstr, + int bits = ssh_key_public_bits(alg, make_ptrlen(blob, bloblen)); + return dupprintf("%.*s %d %s", PTRLEN_PRINTF(algname), bits, fingerprint_str); } else { - return dupprintf("%.*s %s", alglen, algstr, fingerprint_str); + return dupprintf("%.*s %s", PTRLEN_PRINTF(algname), + fingerprint_str); } } else { /* @@ -1619,12 +1537,12 @@ char *ssh2_fingerprint_blob(const void *blob, int bloblen) } } -char *ssh2_fingerprint(const struct ssh_signkey *alg, void *data) +char *ssh2_fingerprint(ssh_key *data) { - int len; - unsigned char *blob = alg->public_blob(data, &len); - char *ret = ssh2_fingerprint_blob(blob, len); - sfree(blob); + strbuf *blob = strbuf_new(); + ssh_key_public_blob(data, BinarySink_UPCAST(blob)); + char *ret = ssh2_fingerprint_blob(blob->s, blob->len); + strbuf_free(blob); return ret; } @@ -1667,7 +1585,8 @@ static int key_type_fp(FILE *fp) (p = p+1 + strspn(p+1, "0123456789"), *p == ' ') && (p = p+1 + strspn(p+1, "0123456789"), *p == ' ' || *p == '\n' || !*p)) return SSH_KEYTYPE_SSH1_PUBLIC; - if ((p = buf + strcspn(buf, " "), find_pubkey_alg_len(p-buf, buf)) && + if ((p = buf + strcspn(buf, " "), + find_pubkey_alg_len(make_ptrlen(buf, p-buf))) && (p = p+1 + strspn(p+1, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij" "klmnopqrstuvwxyz+/="), *p == ' ' || *p == '\n' || !*p)) @@ -1680,7 +1599,7 @@ int key_type(const Filename *filename) FILE *fp; int ret; - fp = f_open(filename, "r", FALSE); + fp = f_open(filename, "r", false); if (!fp) return SSH_KEYTYPE_UNOPENABLE; ret = key_type_fp(fp); diff --git a/sshrand.c b/sshrand.c index 31b1739e..cde35eef 100644 --- a/sshrand.c +++ b/sshrand.c @@ -9,9 +9,6 @@ /* Collect environmental noise every 5 minutes */ #define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC) -void noise_get_heavy(void (*func) (void *, int)); -void noise_get_light(void (*func) (void *, int)); - /* * `pool' itself is a pool of random data which we actually use: we * return bytes from `pool', at position `poolpos', until `poolpos' @@ -42,7 +39,7 @@ struct RandPool { unsigned char incomingb[HASHINPUT]; int incomingpos; - int stir_pending; + bool stir_pending; }; int random_active = 0; @@ -70,8 +67,8 @@ int random_diagnostics = 0; static void random_stir(void) { - word32 block[HASHINPUT / sizeof(word32)]; - word32 digest[HASHSIZE / sizeof(word32)]; + uint32_t block[HASHINPUT / sizeof(uint32_t)]; + uint32_t digest[HASHSIZE / sizeof(uint32_t)]; int i, j, k; /* @@ -80,7 +77,7 @@ static void random_stir(void) */ if (pool.stir_pending) return; - pool.stir_pending = TRUE; + pool.stir_pending = true; noise_get_light(random_add_noise); @@ -91,24 +88,24 @@ static void random_stir(void) for (p = 0; p < POOLSIZE; p += HASHSIZE) { printf(" "); for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(word32 *)(pool.pool + p + q)); + printf(" %08x", *(uint32_t *)(pool.pool + p + q)); } printf("\n"); } printf("incoming:\n "); for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(word32 *)(pool.incoming + q)); + printf(" %08x", *(uint32_t *)(pool.incoming + q)); } printf("\nincomingb:\n "); for (q = 0; q < HASHINPUT; q += 4) { - printf(" %08x", *(word32 *)(pool.incomingb + q)); + printf(" %08x", *(uint32_t *)(pool.incomingb + q)); } printf("\n"); random_diagnostics++; } #endif - SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); + SHATransform((uint32_t *) pool.incoming, (uint32_t *) pool.incomingb); pool.incomingpos = 0; /* @@ -144,7 +141,7 @@ static void random_stir(void) */ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) - digest[k] ^= ((word32 *) (pool.pool + j))[k]; + digest[k] ^= ((uint32_t *) (pool.pool + j))[k]; /* * Munge our unrevealed first block of the pool into @@ -157,7 +154,7 @@ static void random_stir(void) */ for (k = 0; k < sizeof(digest) / sizeof(*digest); k++) - ((word32 *) (pool.pool + j))[k] = digest[k]; + ((uint32_t *) (pool.pool + j))[k] = digest[k]; } #ifdef RANDOM_DIAGNOSTICS @@ -167,17 +164,17 @@ static void random_stir(void) for (p = 0; p < POOLSIZE; p += HASHSIZE) { printf(" "); for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(word32 *)(pool.pool + p + q)); + printf(" %08x", *(uint32_t *)(pool.pool + p + q)); } printf("\n"); } printf("incoming:\n "); for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(word32 *)(pool.incoming + q)); + printf(" %08x", *(uint32_t *)(pool.incoming + q)); } printf("\nincomingb:\n "); for (q = 0; q < HASHINPUT; q += 4) { - printf(" %08x", *(word32 *)(pool.incomingb + q)); + printf(" %08x", *(uint32_t *)(pool.incomingb + q)); } printf("\n"); } @@ -193,7 +190,7 @@ static void random_stir(void) pool.poolpos = sizeof(pool.incoming); - pool.stir_pending = FALSE; + pool.stir_pending = false; #ifdef RANDOM_DIAGNOSTICS { @@ -202,17 +199,17 @@ static void random_stir(void) for (p = 0; p < POOLSIZE; p += HASHSIZE) { printf(" "); for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(word32 *)(pool.pool + p + q)); + printf(" %08x", *(uint32_t *)(pool.pool + p + q)); } printf("\n"); } printf("incoming:\n "); for (q = 0; q < HASHSIZE; q += 4) { - printf(" %08x", *(word32 *)(pool.incoming + q)); + printf(" %08x", *(uint32_t *)(pool.incoming + q)); } printf("\nincomingb:\n "); for (q = 0; q < HASHINPUT; q += 4) { - printf(" %08x", *(word32 *)(pool.incomingb + q)); + printf(" %08x", *(uint32_t *)(pool.incomingb + q)); } printf("\n"); random_diagnostics--; @@ -238,7 +235,7 @@ void random_add_noise(void *noise, int length) HASHINPUT - pool.incomingpos); p += HASHINPUT - pool.incomingpos; length -= HASHINPUT - pool.incomingpos; - SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb); + SHATransform((uint32_t *) pool.incoming, (uint32_t *) pool.incomingb); for (i = 0; i < HASHSIZE; i++) { pool.pool[pool.poolpos++] ^= pool.incoming[i]; if (pool.poolpos >= POOLSIZE) diff --git a/sshrsa.c b/sshrsa.c index e565a64a..2d508777 100644 --- a/sshrsa.c +++ b/sshrsa.c @@ -10,68 +10,46 @@ #include "ssh.h" #include "misc.h" -int makekey(const unsigned char *data, int len, struct RSAKey *result, - const unsigned char **keystr, int order) +void BinarySource_get_rsa_ssh1_pub( + BinarySource *src, struct RSAKey *rsa, RsaSsh1Order order) { - const unsigned char *p = data; - int i, n; - - if (len < 4) - return -1; - - if (result) { - result->bits = 0; - for (i = 0; i < 4; i++) - result->bits = (result->bits << 8) + *p++; - } else - p += 4; - - len -= 4; - - /* - * order=0 means exponent then modulus (the keys sent by the - * server). order=1 means modulus then exponent (the keys - * stored in a keyfile). - */ - - if (order == 0) { - n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL); - if (n < 0) return -1; - p += n; - len -= n; + unsigned bits; + Bignum 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); } - n = ssh1_read_bignum(p, len, result ? &result->modulus : NULL); - if (n < 0 || (result && bignum_bitcount(result->modulus) == 0)) return -1; - if (result) - result->bytes = n - 2; - if (keystr) - *keystr = p + 2; - p += n; - len -= n; - - if (order == 1) { - n = ssh1_read_bignum(p, len, result ? &result->exponent : NULL); - if (n < 0) return -1; - p += n; - len -= n; + if (rsa) { + rsa->bits = bits; + rsa->exponent = e; + rsa->modulus = m; + rsa->bytes = (bignum_bitcount(m) + 7) / 8; + } else { + freebn(e); + freebn(m); } - return p - data; } -int makeprivate(const unsigned char *data, int len, struct RSAKey *result) +void BinarySource_get_rsa_ssh1_priv( + BinarySource *src, struct RSAKey *rsa) { - return ssh1_read_bignum(data, len, &result->private_exponent); + rsa->private_exponent = get_mp_ssh1(src); } -int rsaencrypt(unsigned char *data, int length, struct RSAKey *key) +bool rsa_ssh1_encrypt(unsigned char *data, int length, struct RSAKey *key) { Bignum b1, b2; int i; unsigned char *p; if (key->bytes < length + 4) - return 0; /* RSA key too short! */ + return false; /* RSA key too short! */ memmove(data + key->bytes - length, data, length); data[0] = 0; @@ -96,21 +74,7 @@ int rsaencrypt(unsigned char *data, int length, struct RSAKey *key) freebn(b1); freebn(b2); - return 1; -} - -static void sha512_mpint(SHA512_State * s, Bignum b) -{ - unsigned char lenbuf[4]; - int len; - len = (bignum_bitcount(b) + 8) / 8; - PUT_32BIT(lenbuf, len); - SHA512_Bytes(s, lenbuf, 4); - while (len-- > 0) { - lenbuf[0] = bignum_byte(b, len); - SHA512_Bytes(s, lenbuf, 1); - } - smemclr(lenbuf, sizeof(lenbuf)); + return true; } /* @@ -237,12 +201,10 @@ static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key) * byte = random_byte(); */ if (digestused >= lenof(digest512)) { - unsigned char seqbuf[4]; - PUT_32BIT(seqbuf, hashseq); SHA512_Init(&ss); - SHA512_Bytes(&ss, "RSA deterministic blinding", 26); - SHA512_Bytes(&ss, seqbuf, sizeof(seqbuf)); - sha512_mpint(&ss, key->private_exponent); + put_data(&ss, "RSA deterministic blinding", 26); + put_uint32(&ss, hashseq); + put_mp_ssh2(&ss, key->private_exponent); SHA512_Final(&ss, digest512); hashseq++; @@ -251,8 +213,8 @@ static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key) * input. */ SHA512_Init(&ss); - SHA512_Bytes(&ss, digest512, sizeof(digest512)); - sha512_mpint(&ss, input); + put_data(&ss, digest512, sizeof(digest512)); + put_mp_ssh2(&ss, input); SHA512_Final(&ss, digest512); digestused = 0; @@ -318,11 +280,47 @@ static Bignum rsa_privkey_op(Bignum input, struct RSAKey *key) return ret; } -Bignum rsadecrypt(Bignum input, struct RSAKey *key) +Bignum rsa_ssh1_decrypt(Bignum input, struct RSAKey *key) { return rsa_privkey_op(input, key); } +bool rsa_ssh1_decrypt_pkcs1(Bignum input, struct RSAKey *key, strbuf *outbuf) +{ + strbuf *data = strbuf_new(); + bool success = false; + BinarySource src[1]; + + { + Bignum *b = rsa_ssh1_decrypt(input, key); + int i; + for (i = (bignum_bitcount(key->modulus) + 7) / 8; i-- > 0 ;) { + put_byte(data, bignum_byte(b, i)); + } + freebn(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; +} + int rsastr_len(struct RSAKey *key) { Bignum md, ex; @@ -367,38 +365,25 @@ void rsastr_fmt(char *str, struct RSAKey *key) * Generate a fingerprint string for the key. Compatible with the * OpenSSH fingerprint code. */ -void rsa_fingerprint(char *str, int len, struct RSAKey *key) +char *rsa_ssh1_fingerprint(struct RSAKey *key) { struct MD5Context md5c; unsigned char digest[16]; - char buffer[16 * 3 + 40]; - int numlen, slen, i; + strbuf *out; + int i; MD5Init(&md5c); - numlen = ssh1_bignum_length(key->modulus) - 2; - for (i = numlen; i--;) { - unsigned char c = bignum_byte(key->modulus, i); - MD5Update(&md5c, &c, 1); - } - numlen = ssh1_bignum_length(key->exponent) - 2; - for (i = numlen; i--;) { - unsigned char c = bignum_byte(key->exponent, i); - MD5Update(&md5c, &c, 1); - } + put_mp_ssh1(&md5c, key->modulus); + put_mp_ssh1(&md5c, key->exponent); MD5Final(digest, &md5c); - sprintf(buffer, "%d ", bignum_bitcount(key->modulus)); + out = strbuf_new(); + strbuf_catf(out, "%d ", bignum_bitcount(key->modulus)); for (i = 0; i < 16; i++) - sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "", - digest[i]); - strncpy(str, buffer, len); - str[len - 1] = '\0'; - slen = strlen(str); - if (key->comment && slen < len - 1) { - str[slen] = ' '; - strncpy(str + slen + 1, key->comment, len - slen - 1); - str[len - 1] = '\0'; - } + strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); + if (key->comment) + strbuf_catf(out, " %s", key->comment); + return strbuf_to_str(out); } /* @@ -406,7 +391,7 @@ void rsa_fingerprint(char *str, int len, struct RSAKey *key) * data. We also check the private data itself: we ensure that p > * q and that iqmp really is the inverse of q mod p. */ -int rsa_verify(struct RSAKey *key) +bool rsa_verify(struct RSAKey *key) { Bignum n, ed, pm1, qm1; int cmp; @@ -416,7 +401,7 @@ int rsa_verify(struct RSAKey *key) cmp = bignum_cmp(n, key->modulus); freebn(n); if (cmp != 0) - return 0; + return false; /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ pm1 = copybn(key->p); @@ -426,7 +411,7 @@ int rsa_verify(struct RSAKey *key) cmp = bignum_cmp(ed, One); freebn(ed); if (cmp != 0) - return 0; + return false; qm1 = copybn(key->q); decbn(qm1); @@ -435,7 +420,7 @@ int rsa_verify(struct RSAKey *key) cmp = bignum_cmp(ed, One); freebn(ed); if (cmp != 0) - return 0; + return false; /* * Ensure p > q. @@ -453,7 +438,7 @@ int rsa_verify(struct RSAKey *key) freebn(key->iqmp); key->iqmp = modinv(key->q, key->p); if (!key->iqmp) - return 0; + return false; } /* @@ -463,52 +448,42 @@ int rsa_verify(struct RSAKey *key) cmp = bignum_cmp(n, One); freebn(n); if (cmp != 0) - return 0; + return false; - return 1; + return true; } -/* Public key blob as used by Pageant: exponent before modulus. */ -unsigned char *rsa_public_blob(struct RSAKey *key, int *len) +void rsa_ssh1_public_blob(BinarySink *bs, struct RSAKey *key, + RsaSsh1Order order) { - int length, pos; - unsigned char *ret; - - length = (ssh1_bignum_length(key->modulus) + - ssh1_bignum_length(key->exponent) + 4); - ret = snewn(length, unsigned char); - - PUT_32BIT(ret, bignum_bitcount(key->modulus)); - pos = 4; - pos += ssh1_write_bignum(ret + pos, key->exponent); - pos += ssh1_write_bignum(ret + pos, key->modulus); - - *len = length; - return ret; + put_uint32(bs, bignum_bitcount(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); + } } -/* Given a public blob, determine its length. */ -int rsa_public_blob_len(void *data, int maxlen) +/* Given an SSH-1 public key blob, determine its length. */ +int rsa_ssh1_public_blob_len(void *data, int maxlen) { - unsigned char *p = (unsigned char *)data; - int n; + BinarySource src[1]; - if (maxlen < 4) - return -1; - p += 4; /* length word */ - maxlen -= 4; + BinarySource_BARE_INIT(src, data, maxlen); - n = ssh1_read_bignum(p, maxlen, NULL); /* exponent */ - if (n < 0) - return -1; - p += n; + /* Expect a length word, then exponent and modulus. (It doesn't + * even matter which order.) */ + get_uint32(src); + freebn(get_mp_ssh1(src)); + freebn(get_mp_ssh1(src)); - n = ssh1_read_bignum(p, maxlen, NULL); /* modulus */ - if (n < 0) + if (get_err(src)) return -1; - p += n; - return p - (unsigned char *)data; + /* Return the number of bytes consumed. */ + return src->pos; } void freersakey(struct RSAKey *key) @@ -533,76 +508,43 @@ void freersakey(struct RSAKey *key) * Implementation of the ssh-rsa signing key type. */ -static void getstring(const char **data, int *datalen, - const char **p, int *length) -{ - *p = NULL; - if (*datalen < 4) - return; - *length = toint(GET_32BIT(*data)); - if (*length < 0) - return; - *datalen -= 4; - *data += 4; - if (*datalen < *length) - return; - *p = *data; - *data += *length; - *datalen -= *length; -} -static Bignum getmp(const char **data, int *datalen) -{ - const char *p; - int length; - Bignum b; +static void rsa2_freekey(ssh_key *key); /* forward reference */ - getstring(data, datalen, &p, &length); - if (!p) - return NULL; - b = bignum_from_bytes((unsigned char *)p, length); - return b; -} - -static void rsa2_freekey(void *key); /* forward reference */ - -static void *rsa2_newkey(const struct ssh_signkey *self, - const char *data, int len) +static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data) { - const char *p; - int slen; + BinarySource src[1]; struct RSAKey *rsa; - rsa = snew(struct RSAKey); - getstring(&data, &len, &p, &slen); - - if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { - sfree(rsa); + BinarySource_BARE_INIT(src, data.ptr, data.len); + if (!ptrlen_eq_string(get_string(src), "ssh-rsa")) return NULL; - } - rsa->exponent = getmp(&data, &len); - rsa->modulus = getmp(&data, &len); + + rsa = snew(struct RSAKey); + rsa->sshk = &ssh_rsa; + 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 (!rsa->exponent || !rsa->modulus) { - rsa2_freekey(rsa); - return NULL; + if (get_err(src)) { + rsa2_freekey(&rsa->sshk); + return NULL; } - return rsa; + return &rsa->sshk; } -static void rsa2_freekey(void *key) +static void rsa2_freekey(ssh_key *key) { - struct RSAKey *rsa = (struct RSAKey *) key; + struct RSAKey *rsa = container_of(key, struct RSAKey, sshk); freersakey(rsa); sfree(rsa); } -static char *rsa2_fmtkey(void *key) +static char *rsa2_cache_str(ssh_key *key) { - struct RSAKey *rsa = (struct RSAKey *) key; + struct RSAKey *rsa = container_of(key, struct RSAKey, sshk); char *p; int len; @@ -612,171 +554,100 @@ static char *rsa2_fmtkey(void *key) return p; } -static unsigned char *rsa2_public_blob(void *key, int *len) +static void rsa2_public_blob(ssh_key *key, BinarySink *bs) { - struct RSAKey *rsa = (struct RSAKey *) key; - int elen, mlen, bloblen; - int i; - unsigned char *blob, *p; + struct RSAKey *rsa = container_of(key, struct RSAKey, sshk); - elen = (bignum_bitcount(rsa->exponent) + 8) / 8; - mlen = (bignum_bitcount(rsa->modulus) + 8) / 8; - - /* - * string "ssh-rsa", mpint exp, mpint mod. Total 19+elen+mlen. - * (three length fields, 12+7=19). - */ - bloblen = 19 + elen + mlen; - blob = snewn(bloblen, unsigned char); - p = blob; - PUT_32BIT(p, 7); - p += 4; - memcpy(p, "ssh-rsa", 7); - p += 7; - PUT_32BIT(p, elen); - p += 4; - for (i = elen; i--;) - *p++ = bignum_byte(rsa->exponent, i); - PUT_32BIT(p, mlen); - p += 4; - for (i = mlen; i--;) - *p++ = bignum_byte(rsa->modulus, i); - assert(p == blob + bloblen); - *len = bloblen; - return blob; + put_stringz(bs, "ssh-rsa"); + put_mp_ssh2(bs, rsa->exponent); + put_mp_ssh2(bs, rsa->modulus); } -static unsigned char *rsa2_private_blob(void *key, int *len) +static void rsa2_private_blob(ssh_key *key, BinarySink *bs) { - struct RSAKey *rsa = (struct RSAKey *) key; - int dlen, plen, qlen, ulen, bloblen; - int i; - unsigned char *blob, *p; - - dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8; - plen = (bignum_bitcount(rsa->p) + 8) / 8; - qlen = (bignum_bitcount(rsa->q) + 8) / 8; - ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8; + struct RSAKey *rsa = container_of(key, struct RSAKey, sshk); - /* - * mpint private_exp, mpint p, mpint q, mpint iqmp. Total 16 + - * sum of lengths. - */ - bloblen = 16 + dlen + plen + qlen + ulen; - blob = snewn(bloblen, unsigned char); - p = blob; - PUT_32BIT(p, dlen); - p += 4; - for (i = dlen; i--;) - *p++ = bignum_byte(rsa->private_exponent, i); - PUT_32BIT(p, plen); - p += 4; - for (i = plen; i--;) - *p++ = bignum_byte(rsa->p, i); - PUT_32BIT(p, qlen); - p += 4; - for (i = qlen; i--;) - *p++ = bignum_byte(rsa->q, i); - PUT_32BIT(p, ulen); - p += 4; - for (i = ulen; i--;) - *p++ = bignum_byte(rsa->iqmp, i); - assert(p == blob + bloblen); - *len = bloblen; - return blob; + 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 void *rsa2_createkey(const struct ssh_signkey *self, - const unsigned char *pub_blob, int pub_len, - const unsigned char *priv_blob, int priv_len) +static ssh_key *rsa2_new_priv(const ssh_keyalg *self, + ptrlen pub, ptrlen priv) { + BinarySource src[1]; + ssh_key *sshk; struct RSAKey *rsa; - const char *pb = (const char *) priv_blob; - rsa = rsa2_newkey(self, (char *) pub_blob, pub_len); - rsa->private_exponent = getmp(&pb, &priv_len); - rsa->p = getmp(&pb, &priv_len); - rsa->q = getmp(&pb, &priv_len); - rsa->iqmp = getmp(&pb, &priv_len); + sshk = rsa2_new_pub(self, pub); + if (!sshk) + return NULL; + + rsa = container_of(sshk, struct RSAKey, sshk); + BinarySource_BARE_INIT(src, priv.ptr, priv.len); + 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 (!rsa_verify(rsa)) { - rsa2_freekey(rsa); + if (get_err(src) || !rsa_verify(rsa)) { + rsa2_freekey(&rsa->sshk); return NULL; } - return rsa; + return &rsa->sshk; } -static void *rsa2_openssh_createkey(const struct ssh_signkey *self, - const unsigned char **blob, int *len) +static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) { - const char **b = (const char **) blob; struct RSAKey *rsa; rsa = snew(struct RSAKey); + rsa->sshk = &ssh_rsa; rsa->comment = NULL; - rsa->modulus = getmp(b, len); - rsa->exponent = getmp(b, len); - rsa->private_exponent = getmp(b, len); - rsa->iqmp = getmp(b, len); - rsa->p = getmp(b, len); - rsa->q = getmp(b, len); - - if (!rsa->modulus || !rsa->exponent || !rsa->private_exponent || - !rsa->iqmp || !rsa->p || !rsa->q) { - rsa2_freekey(rsa); - return 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 (!rsa_verify(rsa)) { - rsa2_freekey(rsa); + if (get_err(src) || !rsa_verify(rsa)) { + rsa2_freekey(&rsa->sshk); return NULL; } - return rsa; + return &rsa->sshk; } -static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len) +static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs) { - struct RSAKey *rsa = (struct RSAKey *) key; - int bloblen, i; - - bloblen = - ssh2_bignum_length(rsa->modulus) + - ssh2_bignum_length(rsa->exponent) + - ssh2_bignum_length(rsa->private_exponent) + - ssh2_bignum_length(rsa->iqmp) + - ssh2_bignum_length(rsa->p) + ssh2_bignum_length(rsa->q); - - if (bloblen > len) - return bloblen; - - bloblen = 0; -#define ENC(x) \ - PUT_32BIT(blob+bloblen, ssh2_bignum_length((x))-4); bloblen += 4; \ - for (i = ssh2_bignum_length((x))-4; i-- ;) blob[bloblen++]=bignum_byte((x),i); - ENC(rsa->modulus); - ENC(rsa->exponent); - ENC(rsa->private_exponent); - ENC(rsa->iqmp); - ENC(rsa->p); - ENC(rsa->q); - - return bloblen; + struct RSAKey *rsa = container_of(key, struct 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 struct ssh_signkey *self, - const void *blob, int len) +static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub) { + ssh_key *sshk; struct RSAKey *rsa; int ret; - rsa = rsa2_newkey(self, (const char *) blob, len); - if (!rsa) - return -1; + sshk = rsa2_new_pub(self, pub); + if (!sshk) + return -1; + + rsa = container_of(sshk, struct RSAKey, sshk); ret = bignum_bitcount(rsa->modulus); - rsa2_freekey(rsa); + rsa2_freekey(&rsa->sshk); return ret; } @@ -812,60 +683,70 @@ static const unsigned char asn1_weird_stuff[] = { #define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) ) -static int rsa2_verifysig(void *key, const char *sig, int siglen, - const char *data, int datalen) +static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data) { - struct RSAKey *rsa = (struct RSAKey *) key; + struct RSAKey *rsa = container_of(key, struct RSAKey, sshk); + BinarySource src[1]; + ptrlen type, in_pl; Bignum in, out; - const char *p; - int slen; - int bytes, i, j, ret; + int bytes, i, j; + bool toret; unsigned char hash[20]; - getstring(&sig, &siglen, &p, &slen); - if (!p || slen != 7 || memcmp(p, "ssh-rsa", 7)) { - return 0; - } - in = getmp(&sig, &siglen); - if (!in) - return 0; + BinarySource_BARE_INIT(src, sig.ptr, sig.len); + 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 + * bignum_from_bytes, which will tolerate anything. + */ + in_pl = get_string(src); + if (get_err(src) || !ptrlen_eq_string(type, "ssh-rsa")) + return false; + + in = bignum_from_bytes(in_pl.ptr, in_pl.len); out = modpow(in, rsa->exponent, rsa->modulus); freebn(in); - ret = 1; + toret = true; bytes = (bignum_bitcount(rsa->modulus)+7) / 8; /* Top (partial) byte should be zero. */ if (bignum_byte(out, bytes - 1) != 0) - ret = 0; + toret = false; /* First whole byte should be 1. */ if (bignum_byte(out, bytes - 2) != 1) - ret = 0; + toret = false; /* Most of the rest should be FF. */ for (i = bytes - 3; i >= 20 + ASN1_LEN; i--) { if (bignum_byte(out, i) != 0xFF) - ret = 0; + toret = false; } /* Then we expect to see the asn1_weird_stuff. */ for (i = 20 + ASN1_LEN - 1, j = 0; i >= 20; i--, j++) { if (bignum_byte(out, i) != asn1_weird_stuff[j]) - ret = 0; + toret = false; } /* Finally, we expect to see the SHA-1 hash of the signed data. */ - SHA_Simple(data, datalen, hash); + SHA_Simple(data.ptr, data.len, hash); for (i = 19, j = 0; i >= 0; i--, j++) { if (bignum_byte(out, i) != hash[j]) - ret = 0; + toret = false; } freebn(out); - return ret; + return toret; } -static unsigned char *rsa2_sign(void *key, const char *data, int datalen, - int *siglen) +static void rsa2_sign(ssh_key *key, const void *data, int datalen, + BinarySink *bs) { - struct RSAKey *rsa = (struct RSAKey *) key; + struct RSAKey *rsa = container_of(key, struct RSAKey, sshk); unsigned char *bytes; int nbytes; unsigned char hash[20]; @@ -892,55 +773,54 @@ static unsigned char *rsa2_sign(void *key, const char *data, int datalen, out = rsa_privkey_op(in, rsa); freebn(in); + put_stringz(bs, "ssh-rsa"); nbytes = (bignum_bitcount(out) + 7) / 8; - bytes = snewn(4 + 7 + 4 + nbytes, unsigned char); - PUT_32BIT(bytes, 7); - memcpy(bytes + 4, "ssh-rsa", 7); - PUT_32BIT(bytes + 4 + 7, nbytes); + put_uint32(bs, nbytes); for (i = 0; i < nbytes; i++) - bytes[4 + 7 + 4 + i] = bignum_byte(out, nbytes - 1 - i); - freebn(out); + put_byte(bs, bignum_byte(out, nbytes - 1 - i)); - *siglen = 4 + 7 + 4 + nbytes; - return bytes; + freebn(out); } -const struct ssh_signkey ssh_rsa = { - rsa2_newkey, +const ssh_keyalg ssh_rsa = { + rsa2_new_pub, + rsa2_new_priv, + rsa2_new_priv_openssh, + rsa2_freekey, - rsa2_fmtkey, + rsa2_sign, + rsa2_verify, rsa2_public_blob, rsa2_private_blob, - rsa2_createkey, - rsa2_openssh_createkey, - rsa2_openssh_fmtkey, - 6 /* n,e,d,iqmp,q,p */, + rsa2_openssh_blob, + rsa2_cache_str, + rsa2_pubkey_bits, - rsa2_verifysig, - rsa2_sign, + "ssh-rsa", "rsa2", NULL, }; -void *ssh_rsakex_newkey(char *data, int len) +struct RSAKey *ssh_rsakex_newkey(const void *data, int len) { - return rsa2_newkey(&ssh_rsa, data, len); + ssh_key *sshk = rsa2_new_pub(&ssh_rsa, make_ptrlen(data, len)); + if (!sshk) + return NULL; + return container_of(sshk, struct RSAKey, sshk); } -void ssh_rsakex_freekey(void *key) +void ssh_rsakex_freekey(struct RSAKey *key) { - rsa2_freekey(key); + rsa2_freekey(&key->sshk); } -int ssh_rsakex_klen(void *key) +int ssh_rsakex_klen(struct RSAKey *rsa) { - struct RSAKey *rsa = (struct RSAKey *) key; - return bignum_bitcount(rsa->modulus); } -static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen, +static void oaep_mask(const struct ssh_hashalg *h, void *seed, int seedlen, void *vdata, int datalen) { unsigned char *data = (unsigned char *)vdata; @@ -948,15 +828,14 @@ static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen, while (datalen > 0) { int i, max = (datalen > h->hlen ? h->hlen : datalen); - void *s; - unsigned char counter[4], hash[SSH2_KEX_MAX_HASH_LEN]; + ssh_hash *s; + unsigned char hash[SSH2_KEX_MAX_HASH_LEN]; assert(h->hlen <= SSH2_KEX_MAX_HASH_LEN); - PUT_32BIT(counter, count); - s = h->init(); - h->bytes(s, seed, seedlen); - h->bytes(s, counter, 4); - h->final(s, hash); + s = ssh_hash_new(h); + put_data(s, seed, seedlen); + put_uint32(s, count); + ssh_hash_final(s, hash); count++; for (i = 0; i < max; i++) @@ -967,12 +846,11 @@ static void oaep_mask(const struct ssh_hash *h, void *seed, int seedlen, } } -void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, - unsigned char *out, int outlen, - void *key) +void ssh_rsakex_encrypt(const struct ssh_hashalg *h, + unsigned char *in, int inlen, + unsigned char *out, int outlen, struct RSAKey *rsa) { Bignum b1, b2; - struct RSAKey *rsa = (struct RSAKey *) key; int k, i; char *p; const int HLEN = h->hlen; @@ -1024,7 +902,10 @@ void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, out[i + 1] = random_byte(); /* At position 1+HLEN, the data block DB, consisting of: */ /* The hash of the label (we only support an empty label here) */ - h->final(h->init(), out + HLEN + 1); + { + ssh_hash *s = ssh_hash_new(h); + ssh_hash_final(s, 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. */ @@ -1059,12 +940,88 @@ void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen, */ } +Bignum ssh_rsakex_decrypt(const struct ssh_hashalg *h, ptrlen ciphertext, + struct RSAKey *rsa) +{ + Bignum b1, b2; + int outlen, i; + unsigned char *out; + unsigned char labelhash[64]; + ssh_hash *hash; + 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 + bignum_bitcount(rsa->modulus)) / 8; + if (ciphertext.len != outlen) + return NULL; + + /* Do the RSA decryption, and extract the result into a byte array. */ + b1 = bignum_from_bytes(ciphertext.ptr, ciphertext.len); + b2 = rsa_privkey_op(b1, rsa); + out = snewn(outlen, unsigned char); + for (i = 0; i < outlen; i++) + out[i] = bignum_byte(b2, outlen-1-i); + freebn(b1); + freebn(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 = ssh_hash_new(h); + ssh_hash_final(hash, 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] != 1) { + 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) { + freebn(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 struct ssh_kex ssh_rsa_kex_sha1 = { - "rsa1024-sha1", NULL, KEXTYPE_RSA, &ssh_sha1, NULL, + "rsa1024-sha1", NULL, KEXTYPE_RSA, + &ssh_sha1, &ssh_rsa_kex_extra_sha1, }; static const struct ssh_kex ssh_rsa_kex_sha256 = { - "rsa2048-sha256", NULL, KEXTYPE_RSA, &ssh_sha256, NULL, + "rsa2048-sha256", NULL, KEXTYPE_RSA, + &ssh_sha256, &ssh_rsa_kex_extra_sha256, }; static const struct ssh_kex *const rsa_kex_list[] = { diff --git a/sshrsacert.c b/sshrsacert.c new file mode 100644 index 00000000..f3d45f19 --- /dev/null +++ b/sshrsacert.c @@ -0,0 +1,347 @@ +/* + * RSA Certificate implementation for PuTTY. + */ + +#include +#include +#include +#include + +#include "ssh.h" +#include "misc.h" + +/* ----------------------------------------------------------------------- + * Implementation of the ssh-rsa-cert-v01 key type + */ + +static void rsa2cert_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *rsa2cert_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + struct RSACertKey *certkey; + + BinarySource_BARE_INIT(src, data.ptr, data.len); + ptrlen certtype = get_string(src); + if (!ptrlen_eq_string(certtype, self->ssh_id)) + return NULL; + + certkey = snew(struct RSACertKey); + certkey->sshk = self; + + certkey->certificate.ptr = snewn(data.len, char); + memcpy((void*)(certkey->certificate.ptr), data.ptr, data.len); + certkey->certificate.len = data.len; + + certkey->private_exponent = NULL; + certkey->p = certkey->q = certkey->iqmp = NULL; + certkey->comment = NULL; + certkey->modulus = certkey->exponent = NULL; + certkey->nonce = certkey->keyid = certkey->principals = NULL; + certkey->options = certkey->extensions = certkey->reserved = NULL; + certkey->sigkey = NULL; + certkey->signature = NULL; + + if (get_err(src)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + certkey->nonce = mkstr(get_string(src)); + certkey->exponent = get_mp_ssh2(src); + certkey->modulus = get_mp_ssh2(src); + certkey->serial = get_uint64(src); + certkey->type = get_uint32(src); + certkey->keyid = mkstr(get_string(src)); + certkey->principals = mkstr(get_string(src)); + certkey->valid_after = get_uint64(src); + certkey->valid_before = get_uint64(src); + certkey->options = mkstr(get_string(src)); + certkey->extensions = mkstr(get_string(src)); + certkey->reserved = mkstr(get_string(src)); + + ptrlen sigkey = get_string(src); + + certkey->signature = mkstr(get_string(src)); + + if (get_err(src)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } + + if (get_err(sk)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static void rsa2cert_freekey(ssh_key *key) +{ + struct RSACertKey *certkey = container_of(key, struct RSACertKey, sshk); + + if (certkey->certificate.ptr) + sfree((void*)(certkey->certificate.ptr)); + if (certkey->nonce) + sfree(certkey->nonce); + if (certkey->modulus) + freebn(certkey->modulus); + if (certkey->exponent) + freebn(certkey->exponent); + if (certkey->keyid) + sfree(certkey->keyid); + if (certkey->principals) + sfree(certkey->principals); + if (certkey->options) + sfree(certkey->options); + if (certkey->extensions) + sfree(certkey->extensions); + if (certkey->reserved) + sfree(certkey->reserved); + if (certkey->sigkey) + ssh_key_free(certkey->sigkey); + if (certkey->signature) + sfree(certkey->signature); + if (certkey->private_exponent) + freebn(certkey->private_exponent); + if (certkey->iqmp) + freebn(certkey->iqmp); + if (certkey->p) + freebn(certkey->p); + if (certkey->q) + freebn(certkey->q); + if (certkey->comment) + sfree(certkey->comment); + + sfree(certkey); +} + +static ssh_key *rsa2cert_new_priv(const ssh_keyalg *self, + ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + struct RSACertKey *certkey; + + sshk = ssh_key_new_pub(self, pub); + if (!sshk) { + return NULL; + } + + certkey = container_of(sshk, struct RSACertKey, sshk); + BinarySource_BARE_INIT(src, priv.ptr, priv.len); + certkey->private_exponent = get_mp_ssh2(src); + certkey->p = get_mp_ssh2(src); + certkey->q = get_mp_ssh2(src); + certkey->iqmp = get_mp_ssh2(src); + certkey->comment = NULL; + + struct RSAKey rsakey; + + rsakey.modulus = certkey->modulus; + rsakey.exponent = certkey->exponent; + rsakey.private_exponent = certkey->private_exponent; + rsakey.p = certkey->p; + rsakey.q = certkey->q; + rsakey.iqmp = certkey->iqmp; + rsakey.comment = ""; + rsakey.sshk = &ssh_rsa; + + if (get_err(src) || !rsa_verify(&rsakey)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static ssh_key *rsa2cert_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + struct RSACertKey *certkey; + + certkey = snew(struct RSACertKey); + certkey->sshk = self; + certkey->comment = NULL; + + ptrlen certdata = get_string(src); + certkey->certificate.ptr = snewn(certdata.len, char); + memcpy((void*)(certkey->certificate.ptr), certdata.ptr, certdata.len); + certkey->certificate.len = certdata.len; + certkey->private_exponent = get_mp_ssh2(src); + certkey->iqmp = get_mp_ssh2(src); + certkey->p = get_mp_ssh2(src); + certkey->q = get_mp_ssh2(src); + + if (get_err(src)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource cert[1]; + BinarySource_BARE_INIT(cert, certkey->certificate.ptr, certkey->certificate.len); + ptrlen certtype = get_string(cert); + + certkey->nonce = mkstr(get_string(cert)); + certkey->exponent = get_mp_ssh2(cert); + certkey->modulus = get_mp_ssh2(cert); + certkey->serial = get_uint64(cert); + certkey->type = get_uint32(cert); + certkey->keyid = mkstr(get_string(cert)); + certkey->principals = mkstr(get_string(cert)); + certkey->valid_after = get_uint64(cert); + certkey->valid_before = get_uint64(cert); + certkey->options = mkstr(get_string(cert)); + certkey->extensions = mkstr(get_string(cert)); + certkey->reserved = mkstr(get_string(cert)); + + ptrlen sigkey = get_string(cert); + + certkey->signature = mkstr(get_string(cert)); + + struct RSAKey rsakey; + + rsakey.modulus = certkey->modulus; + rsakey.exponent = certkey->exponent; + rsakey.private_exponent = certkey->private_exponent; + rsakey.p = certkey->p; + rsakey.q = certkey->q; + rsakey.iqmp = certkey->iqmp; + rsakey.comment = ""; + rsakey.sshk = &ssh_rsa; + + if (get_err(cert) || !ptrlen_eq_string(certtype, self->ssh_id) + || !rsa_verify(&rsakey)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + BinarySource sk[1]; + BinarySource_BARE_INIT(sk, sigkey.ptr, sigkey.len); + ptrlen algname = get_string(sk); + ssh_key signature_key = find_pubkey_alg_len(algname); + if (signature_key != NULL) { + certkey->sigkey = ssh_key_new_pub(signature_key, get_data(sk, get_avail(sk))); + } else { + certkey->sigkey = NULL; + } + + if (get_err(sk)) { + rsa2cert_freekey(&certkey->sshk); + return NULL; + } + + return &certkey->sshk; +} + +static void rsa2cert_sign(ssh_key *key, const void* data, int datalen, + BinarySink *bs) +{ + struct RSACertKey *certkey = container_of(key, struct RSACertKey, sshk); + struct RSAKey rsakey; + + rsakey.modulus = certkey->modulus; + rsakey.exponent = certkey->exponent; + rsakey.private_exponent = certkey->private_exponent; + rsakey.p = certkey->p; + rsakey.q = certkey->q; + rsakey.iqmp = certkey->iqmp; + rsakey.comment = ""; + rsakey.sshk = &ssh_rsa; + + return ssh_key_sign(&rsakey.sshk, data, datalen, bs); +} + +static bool rsa2cert_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct RSACertKey *certkey = container_of(key, struct RSACertKey, sshk); + struct RSAKey rsakey; + + rsakey.modulus = certkey->modulus; + rsakey.exponent = certkey->exponent; + rsakey.private_exponent = certkey->private_exponent; + rsakey.p = certkey->p; + rsakey.q = certkey->q; + rsakey.iqmp = certkey->iqmp; + rsakey.comment = ""; + rsakey.sshk = &ssh_rsa; + + return ssh_key_verify(&rsakey.sshk, sig, data); +} + +static void rsa2cert_public_blob(ssh_key *key, BinarySink *bs) +{ + struct RSACertKey *certkey = container_of(key, struct RSACertKey, sshk); + + // copy the certificate + put_data(bs, certkey->certificate.ptr, certkey->certificate.len); +} + +static void rsa2cert_private_blob(ssh_key *key, BinarySink *bs) +{ + struct RSACertKey *rsa = container_of(key, struct RSACertKey, 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 void rsa2cert_openssh_blob(ssh_key* key, BinarySink *bs) +{ + // don't return anything. USed only for export, and we don't export certs +} + +// Used just for looking up host keys for now, so skip +static char * rsa2cert_cache_str(ssh_key *key) +{ + char *p = snewn(1, char); + p[0] = '\0'; + return p; +} + +static int rsa2cert_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + struct RSACertKey *certkey; + int ret; + + sshk = ssh_key_new_pub(self, pub); + if (!sshk) + return -1; + + certkey = container_of(sshk, struct RSACertKey, sshk); + ret = bignum_bitcount(certkey->modulus); + rsa2cert_freekey(&certkey->sshk); + + return ret; +} + +const ssh_keyalg ssh_cert_rsa = { + rsa2cert_new_pub, + rsa2cert_new_priv, + rsa2cert_new_priv_openssh, + + rsa2cert_freekey, + rsa2cert_sign, + rsa2cert_verify, + rsa2cert_public_blob, + rsa2cert_private_blob, + rsa2cert_openssh_blob, + rsa2cert_cache_str, + + rsa2cert_pubkey_bits, + + "ssh-rsa-cert-v01@openssh.com", + "ssh-rsa-cert-v01", + NULL, +}; diff --git a/sshrsag.c b/sshrsag.c index d754890d..7800ec94 100644 --- a/sshrsag.c +++ b/sshrsag.c @@ -14,6 +14,8 @@ int rsa_generate(struct RSAKey *key, int bits, progfn_t pfn, Bignum pm1, qm1, phi_n; unsigned pfirst, qfirst; + key->sshk = &ssh_rsa; + /* * Set up the phase limits for the progress report. We do this * by passing minus the phase number. diff --git a/sshserver.c b/sshserver.c new file mode 100644 index 00000000..b43432dc --- /dev/null +++ b/sshserver.c @@ -0,0 +1,468 @@ +/* + * Top-level code for SSH server implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.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; + + 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; + ssh_key *const *hostkeys; + int nhostkeys; + struct RSAKey *hostkey1; + AuthPolicy *authpolicy; + const SftpServerVtable *sftpserver_vt; + + Seat seat; + Ssh ssh; + struct ssh_version_receiver version_receiver; + + BinaryPacketProtocol *bpp; + PacketProtocolLayer *base_layer; + ConnectionLayer *cl; + + struct ssh_connection_shared_gss_state gss_state; +}; + +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) {} + +mainchan *mainchan_new( + PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, + int term_width, int term_height, int 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 = { + nullseat_output, + nullseat_eof, + nullseat_get_userpass_input, + nullseat_notify_remote_exit, + nullseat_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + nullseat_verify_ssh_host_key, + server_confirm_weak_crypto_primitive, + server_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + nullseat_get_window_pixel_size, +}; + +static void server_socket_log(Plug *plug, int 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, "Network error: %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, char *data, int 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, int 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); + } +} + +static const PlugVtable ssh_server_plugvt = { + server_socket_log, + server_closing, + server_receive, + server_sent, + NULL +}; + +Plug *ssh_server_plug( + Conf *conf, ssh_key *const *hostkeys, int nhostkeys, + struct 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->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->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); + + /* FIXME: replace with sensible */ + srv->gss_state.libs = snew(struct ssh_gss_liblist); + srv->gss_state.libs->nlibraries = 0; + + return &srv->plug; +} + +void ssh_server_start(Plug *plug, Socket *socket) +{ + server *srv = container_of(plug, server, plug); + const char *our_protoversion; + + 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, false /* bare_connection */, + our_protoversion, &srv->version_receiver, + true, "Uppity"); + server_connect_bpp(srv); + queue_idempotent_callback(&srv->bpp->ic_in_raw); +} + +static void ssh_server_free_callback(void *vsrv) +{ + server *srv = (server *)vsrv; + + bufchain_clear(&srv->in_raw); + bufchain_clear(&srv->out_raw); + bufchain_clear(&srv->dummy_user_input); + + sk_close(srv->socket); + + if (srv->bpp) + ssh_bpp_free(srv->bpp); + + delete_callbacks_for_context(srv); + + conf_free(srv->conf); + log_free(srv->logctx); + + sfree(srv->gss_state.libs); /* FIXME: replace with sensible */ + + sfree(srv); + + server_instance_terminated(); +} + +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; + srv->bpp->out_raw->ic = &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) { + void *data; + int len, backlog; + + bufchain_prefix(&srv->out_raw, &data, &len); + + if (srv->logctx) + log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data, len, + 0, NULL, NULL, 0, NULL); + backlog = sk_write(srv->socket, data, len); + + bufchain_consume(&srv->out_raw, len); + + if (backlog > SSH_MAX_BACKLOG) { +#ifdef FIXME + ssh_throttle_all(ssh, 1, backlog); +#endif + return; + } + } + +#ifdef FIXME + if (ssh->pending_close) { + sk_close(ssh->s); + ssh->s = NULL; + } +#endif +} + +#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); + LOG_FORMATTED_MSG(srv->logctx, fmt); + queue_toplevel_callback(ssh_server_free_callback, srv); +} + +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 (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); + 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); + 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), + &srv->gss_state, &srv->stats, transport_child_layer, true); + 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); + server_connect_ppl(srv, connection_layer); + + srv->base_layer = ssh1_login_server_new( + connection_layer, srv->hostkey1, srv->authpolicy); + 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 + + 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 new file mode 100644 index 00000000..2dc43d95 --- /dev/null +++ b/sshserver.h @@ -0,0 +1,103 @@ +typedef struct AuthPolicy AuthPolicy; + +Plug *ssh_server_plug( + Conf *conf, ssh_key *const *hostkeys, int nhostkeys, + struct RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, + const SftpServerVtable *sftpserver_vt); +void ssh_server_start(Plug *plug, Socket *socket); + +void server_instance_terminated(void); +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); + +struct RSAKey *auth_publickey_ssh1( + AuthPolicy *ap, ptrlen username, Bignum 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); +void ssh2_userauth_server_set_transport_layer( + PacketProtocolLayer *userauth, PacketProtocolLayer *transport); + +void ssh2connection_server_configure( + PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt); + +PacketProtocolLayer *ssh1_login_server_new( + PacketProtocolLayer *successor_layer, struct RSAKey *hostkey, + AuthPolicy *authpolicy); + +Channel *sesschan_new(SshChannel *c, LogContext *logctx, + const SftpServerVtable *sftpserver_vt); + +Backend *pty_backend_create( + Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, + struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty); +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); diff --git a/sshsh256.c b/sshsh256.c index 4186f3e8..da6782f1 100644 --- a/sshsh256.c +++ b/sshsh256.c @@ -5,13 +5,14 @@ */ #include "ssh.h" +#include /* ---------------------------------------------------------------------- * Core SHA256 algorithm: processes 16-word blocks into a message digest. */ -#define ror(x,y) ( ((x) << (32-y)) | (((uint32)(x)) >> (y)) ) -#define shr(x,y) ( (((uint32)(x)) >> (y)) ) +#define ror(x,y) ( ((x) << (32-y)) | (((uint32_t)(x)) >> (y)) ) +#define shr(x,y) ( (((uint32_t)(x)) >> (y)) ) #define Ch(x,y,z) ( ((x) & (y)) ^ (~(x) & (z)) ) #define Maj(x,y,z) ( ((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)) ) #define bigsigma0(x) ( ror((x),2) ^ ror((x),13) ^ ror((x),22) ) @@ -19,6 +20,9 @@ #define smallsigma0(x) ( ror((x),7) ^ ror((x),18) ^ shr((x),3) ) #define smallsigma1(x) ( ror((x),17) ^ ror((x),19) ^ shr((x),10) ) +static void SHA256_sw(SHA256_State *s, const unsigned char *q, int len); +static void SHA256_ni(SHA256_State * s, const unsigned char *q, int len); + void SHA256_Core_Init(SHA256_State *s) { s->h[0] = 0x6a09e667; s->h[1] = 0xbb67ae85; @@ -30,9 +34,9 @@ void SHA256_Core_Init(SHA256_State *s) { s->h[7] = 0x5be0cd19; } -void SHA256_Block(SHA256_State *s, uint32 *block) { - uint32 w[80]; - uint32 a,b,c,d,e,f,g,h; +void SHA256_Block(SHA256_State *s, uint32_t *block) { + uint32_t w[80]; + uint32_t a,b,c,d,e,f,g,h; static const int k[] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, @@ -64,7 +68,7 @@ void SHA256_Block(SHA256_State *s, uint32 *block) { e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7]; for (t = 0; t < 64; t+=8) { - uint32 t1, t2; + uint32_t t1, t2; #define ROUND(j,a,b,c,d,e,f,g,h) \ t1 = h + bigsigma1(e) + Ch(e,f,g) + k[j] + w[j]; \ @@ -93,23 +97,37 @@ void SHA256_Block(SHA256_State *s, uint32 *block) { #define BLKSIZE 64 +static void SHA256_BinarySink_write(BinarySink *bs, + const void *p, size_t len); + void SHA256_Init(SHA256_State *s) { SHA256_Core_Init(s); s->blkused = 0; - s->lenhi = s->lenlo = 0; + s->len = 0; + if (supports_sha_ni()) + s->sha256 = &SHA256_ni; + else + s->sha256 = &SHA256_sw; + BinarySink_INIT(s, SHA256_BinarySink_write); } -void SHA256_Bytes(SHA256_State *s, const void *p, int len) { +static void SHA256_BinarySink_write(BinarySink *bs, + const void *p, size_t len) +{ + struct SHA256_State *s = BinarySink_DOWNCAST(bs, struct SHA256_State); unsigned char *q = (unsigned char *)p; - uint32 wordblock[16]; - uint32 lenw = len; - int i; /* * Update the length field. */ - s->lenlo += lenw; - s->lenhi += (s->lenlo < lenw); + s->len += len; + + (*(s->sha256))(s, q, len); +} + +static void SHA256_sw(SHA256_State *s, const unsigned char *q, int len) { + uint32_t wordblock[16]; + int i; if (s->blkused && s->blkused+len < BLKSIZE) { /* @@ -128,10 +146,10 @@ void SHA256_Bytes(SHA256_State *s, const void *p, int len) { /* Now process the block. Gather bytes big-endian into words */ for (i = 0; i < 16; i++) { wordblock[i] = - ( ((uint32)s->block[i*4+0]) << 24 ) | - ( ((uint32)s->block[i*4+1]) << 16 ) | - ( ((uint32)s->block[i*4+2]) << 8 ) | - ( ((uint32)s->block[i*4+3]) << 0 ); + ( ((uint32_t)s->block[i*4+0]) << 24 ) | + ( ((uint32_t)s->block[i*4+1]) << 16 ) | + ( ((uint32_t)s->block[i*4+2]) << 8 ) | + ( ((uint32_t)s->block[i*4+3]) << 0 ); } SHA256_Block(s, wordblock); s->blkused = 0; @@ -145,30 +163,20 @@ void SHA256_Final(SHA256_State *s, unsigned char *digest) { int i; int pad; unsigned char c[64]; - uint32 lenhi, lenlo; + uint64_t len; if (s->blkused >= 56) pad = 56 + 64 - s->blkused; else pad = 56 - s->blkused; - lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3)); - lenlo = (s->lenlo << 3); + len = (s->len << 3); memset(c, 0, pad); c[0] = 0x80; - SHA256_Bytes(s, &c, pad); - - c[0] = (lenhi >> 24) & 0xFF; - c[1] = (lenhi >> 16) & 0xFF; - c[2] = (lenhi >> 8) & 0xFF; - c[3] = (lenhi >> 0) & 0xFF; - c[4] = (lenlo >> 24) & 0xFF; - c[5] = (lenlo >> 16) & 0xFF; - c[6] = (lenlo >> 8) & 0xFF; - c[7] = (lenlo >> 0) & 0xFF; + put_data(s, &c, pad); - SHA256_Bytes(s, &c, 8); + put_uint64(s, len); for (i = 0; i < 8; i++) { digest[i*4+0] = (s->h[i] >> 24) & 0xFF; @@ -182,7 +190,7 @@ void SHA256_Simple(const void *p, int len, unsigned char *output) { SHA256_State s; SHA256_Init(&s); - SHA256_Bytes(&s, p, len); + put_data(&s, p, len); SHA256_Final(&s, output); smemclr(&s, sizeof(s)); } @@ -191,51 +199,51 @@ void SHA256_Simple(const void *p, int len, unsigned char *output) { * Thin abstraction for things where hashes are pluggable. */ -static void *sha256_init(void) -{ - SHA256_State *s; +struct sha256_hash { + SHA256_State state; + ssh_hash hash; +}; - s = snew(SHA256_State); - SHA256_Init(s); - return s; +static ssh_hash *sha256_new(const struct ssh_hashalg *alg) +{ + struct sha256_hash *h = snew(struct sha256_hash); + SHA256_Init(&h->state); + h->hash.vt = alg; + BinarySink_DELEGATE_INIT(&h->hash, &h->state); + return &h->hash; } -static void *sha256_copy(const void *vold) +static ssh_hash *sha256_copy(ssh_hash *hashold) { - const SHA256_State *old = (const SHA256_State *)vold; - SHA256_State *s; + struct sha256_hash *hold, *hnew; + ssh_hash *hashnew = sha256_new(hashold->vt); - s = snew(SHA256_State); - *s = *old; - return s; -} + hold = container_of(hashold, struct sha256_hash, hash); + hnew = container_of(hashnew, struct sha256_hash, hash); -static void sha256_free(void *handle) -{ - SHA256_State *s = handle; + hnew->state = hold->state; + BinarySink_COPIED(&hnew->state); - smemclr(s, sizeof(*s)); - sfree(s); + return hashnew; } -static void sha256_bytes(void *handle, const void *p, int len) +static void sha256_free(ssh_hash *hash) { - SHA256_State *s = handle; + struct sha256_hash *h = container_of(hash, struct sha256_hash, hash); - SHA256_Bytes(s, p, len); + smemclr(h, sizeof(*h)); + sfree(h); } -static void sha256_final(void *handle, unsigned char *output) +static void sha256_final(ssh_hash *hash, unsigned char *output) { - SHA256_State *s = handle; - - SHA256_Final(s, output); - sha256_free(s); + struct sha256_hash *h = container_of(hash, struct sha256_hash, hash); + SHA256_Final(&h->state, output); + sha256_free(hash); } -const struct ssh_hash ssh_sha256 = { - sha256_init, sha256_copy, sha256_bytes, sha256_final, sha256_free, - 32, "SHA-256" +const struct ssh_hashalg ssh_sha256 = { + sha256_new, sha256_copy, sha256_final, sha256_free, 32, "SHA-256" }; /* ---------------------------------------------------------------------- @@ -243,107 +251,80 @@ const struct ssh_hash ssh_sha256 = { * HMAC wrapper on it. */ -static void *sha256_make_context(void *cipher_ctx) +struct hmacsha256 { + SHA256_State sha[3]; + ssh2_mac mac; +}; + +static ssh2_mac *hmacsha256_new( + const struct ssh2_macalg *alg, ssh2_cipher *cipher) { - return snewn(3, SHA256_State); + struct hmacsha256 *ctx = snew(struct hmacsha256); + ctx->mac.vt = alg; + BinarySink_DELEGATE_INIT(&ctx->mac, &ctx->sha[2]); + return &ctx->mac; } -static void sha256_free_context(void *handle) +static void hmacsha256_free(ssh2_mac *mac) { - smemclr(handle, 3 * sizeof(SHA256_State)); - sfree(handle); + struct hmacsha256 *ctx = container_of(mac, struct hmacsha256, mac); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void sha256_key_internal(void *handle, unsigned char *key, int len) +static void sha256_key_internal(struct hmacsha256 *ctx, + const unsigned char *key, int len) { - SHA256_State *keys = (SHA256_State *)handle; unsigned char foo[64]; int i; memset(foo, 0x36, 64); for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; - SHA256_Init(&keys[0]); - SHA256_Bytes(&keys[0], foo, 64); + SHA256_Init(&ctx->sha[0]); + put_data(&ctx->sha[0], foo, 64); memset(foo, 0x5C, 64); for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; - SHA256_Init(&keys[1]); - SHA256_Bytes(&keys[1], foo, 64); + SHA256_Init(&ctx->sha[1]); + put_data(&ctx->sha[1], foo, 64); smemclr(foo, 64); /* burn the evidence */ } -static void sha256_key(void *handle, unsigned char *key) +static void hmacsha256_key(ssh2_mac *mac, const void *key) { - sha256_key_internal(handle, key, 32); + struct hmacsha256 *ctx = container_of(mac, struct hmacsha256, mac); + sha256_key_internal(ctx, key, ctx->mac.vt->keylen); } -static void hmacsha256_start(void *handle) +static void hmacsha256_start(ssh2_mac *mac) { - SHA256_State *keys = (SHA256_State *)handle; - - keys[2] = keys[0]; /* structure copy */ -} + struct hmacsha256 *ctx = container_of(mac, struct hmacsha256, mac); -static void hmacsha256_bytes(void *handle, unsigned char const *blk, int len) -{ - SHA256_State *keys = (SHA256_State *)handle; - SHA256_Bytes(&keys[2], (void *)blk, len); + ctx->sha[2] = ctx->sha[0]; /* structure copy */ + BinarySink_COPIED(&ctx->sha[2]); } -static void hmacsha256_genresult(void *handle, unsigned char *hmac) +static void hmacsha256_genresult(ssh2_mac *mac, unsigned char *hmac) { - SHA256_State *keys = (SHA256_State *)handle; + struct hmacsha256 *ctx = container_of(mac, struct hmacsha256, mac); SHA256_State s; unsigned char intermediate[32]; - s = keys[2]; /* structure copy */ + s = ctx->sha[2]; /* structure copy */ + BinarySink_COPIED(&s); SHA256_Final(&s, intermediate); - s = keys[1]; /* structure copy */ - SHA256_Bytes(&s, intermediate, 32); + s = ctx->sha[1]; /* structure copy */ + BinarySink_COPIED(&s); + put_data(&s, intermediate, 32); SHA256_Final(&s, hmac); } -static void sha256_do_hmac(void *handle, unsigned char *blk, int len, - unsigned long seq, unsigned char *hmac) -{ - unsigned char seqbuf[4]; - - PUT_32BIT_MSB_FIRST(seqbuf, seq); - hmacsha256_start(handle); - hmacsha256_bytes(handle, seqbuf, 4); - hmacsha256_bytes(handle, blk, len); - hmacsha256_genresult(handle, hmac); -} - -static void sha256_generate(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - sha256_do_hmac(handle, blk, len, seq, blk + len); -} - -static int hmacsha256_verresult(void *handle, unsigned char const *hmac) -{ - unsigned char correct[32]; - hmacsha256_genresult(handle, correct); - return smemeq(correct, hmac, 32); -} - -static int sha256_verify(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - unsigned char correct[32]; - sha256_do_hmac(handle, blk, len, seq, correct); - return smemeq(correct, blk + len, 32); -} - -const struct ssh_mac ssh_hmac_sha256 = { - sha256_make_context, sha256_free_context, sha256_key, - sha256_generate, sha256_verify, - hmacsha256_start, hmacsha256_bytes, - hmacsha256_genresult, hmacsha256_verresult, +const struct ssh2_macalg ssh_hmac_sha256 = { + hmacsha256_new, hmacsha256_free, hmacsha256_key, + hmacsha256_start, hmacsha256_genresult, "hmac-sha2-256", "hmac-sha2-256-etm@openssh.com", 32, 32, "HMAC-SHA-256" @@ -398,3 +379,260 @@ int main(void) { } #endif + +#ifdef COMPILER_SUPPORTS_SHA_NI + +#if defined _MSC_VER && defined _M_AMD64 +# include +#endif + +/* + * Set target architecture for Clang and GCC + */ +#if !defined(__clang__) && defined(__GNUC__) +# pragma GCC target("sha") +# pragma GCC target("sse4.1") +#endif + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 5)) +# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) +#else +# define FUNC_ISA +#endif + +#include +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +#include +#endif + +/* SHA256 implementation using new instructions + The code is based on Jeffrey Walton's SHA256 implementation: + https://github.com/noloader/SHA-Intrinsics +*/ +FUNC_ISA +static void SHA256_ni_(SHA256_State * s, const unsigned char *q, int len) { + if (s->blkused && s->blkused+len < BLKSIZE) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + __m128i STATE0, STATE1; + __m128i MSG, TMP; + __m128i MSG0, MSG1, MSG2, MSG3; + __m128i ABEF_SAVE, CDGH_SAVE; + const __m128i MASK = _mm_set_epi64x(0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); + + /* Load initial values */ + TMP = _mm_loadu_si128((const __m128i*) &s->h[0]); + STATE1 = _mm_loadu_si128((const __m128i*) &s->h[4]); + + TMP = _mm_shuffle_epi32(TMP, 0xB1); /* CDAB */ + STATE1 = _mm_shuffle_epi32(STATE1, 0x1B); /* EFGH */ + STATE0 = _mm_alignr_epi8(TMP, STATE1, 8); /* ABEF */ + STATE1 = _mm_blend_epi16(STATE1, TMP, 0xF0); /* CDGH */ + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= BLKSIZE) { + memcpy(s->block + s->blkused, q, BLKSIZE - s->blkused); + q += BLKSIZE - s->blkused; + len -= BLKSIZE - s->blkused; + + /* Save current state */ + ABEF_SAVE = STATE0; + CDGH_SAVE = STATE1; + + /* Rounds 0-3 */ + MSG = _mm_loadu_si128((const __m128i*) (s->block + 0)); + 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((const __m128i*) (s->block + 16)); + 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((const __m128i*) (s->block + 32)); + 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((const __m128i*) (s->block + 48)); + 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 */ + STATE0 = _mm_add_epi32(STATE0, ABEF_SAVE); + STATE1 = _mm_add_epi32(STATE1, CDGH_SAVE); + + s->blkused = 0; + } + + TMP = _mm_shuffle_epi32(STATE0, 0x1B); /* FEBA */ + STATE1 = _mm_shuffle_epi32(STATE1, 0xB1); /* DCHG */ + STATE0 = _mm_blend_epi16(TMP, STATE1, 0xF0); /* DCBA */ + STATE1 = _mm_alignr_epi8(STATE1, TMP, 8); /* ABEF */ + + /* Save state */ + _mm_storeu_si128((__m128i*) &s->h[0], STATE0); + _mm_storeu_si128((__m128i*) &s->h[4], STATE1); + + memcpy(s->block, q, len); + s->blkused = len; + } +} + +/* + * Workaround LLVM bug https://bugs.llvm.org/show_bug.cgi?id=34980 + */ +static void SHA256_ni(SHA256_State * s, const unsigned char *q, int len) +{ + SHA256_ni_(s, q, len); +} + +#else /* COMPILER_SUPPORTS_AES_NI */ + +static void SHA256_ni(SHA256_State * s, const unsigned char *q, int len) +{ + assert(0); +} + +#endif /* COMPILER_SUPPORTS_AES_NI */ diff --git a/sshsh512.c b/sshsh512.c index bdfc1e9d..09abf587 100644 --- a/sshsh512.c +++ b/sshsh512.c @@ -6,6 +6,7 @@ * Modifications made for SHA-384 also */ +#include #include "ssh.h" #define BLKSIZE 128 @@ -14,21 +15,17 @@ * Arithmetic implementations. Note that AND, XOR and NOT can * overlap destination with one source, but the others can't. */ -#define add(r,x,y) ( r.lo = y.lo + x.lo, \ - r.hi = y.hi + x.hi + ((uint32)r.lo < (uint32)y.lo) ) -#define rorB(r,x,y) ( r.lo = ((uint32)x.hi >> ((y)-32)) | ((uint32)x.lo << (64-(y))), \ - r.hi = ((uint32)x.lo >> ((y)-32)) | ((uint32)x.hi << (64-(y))) ) -#define rorL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \ - r.hi = ((uint32)x.hi >> (y)) | ((uint32)x.lo << (32-(y))) ) -#define shrB(r,x,y) ( r.lo = (uint32)x.hi >> ((y)-32), r.hi = 0 ) -#define shrL(r,x,y) ( r.lo = ((uint32)x.lo >> (y)) | ((uint32)x.hi << (32-(y))), \ - r.hi = (uint32)x.hi >> (y) ) -#define and(r,x,y) ( r.lo = x.lo & y.lo, r.hi = x.hi & y.hi ) -#define xor(r,x,y) ( r.lo = x.lo ^ y.lo, r.hi = x.hi ^ y.hi ) -#define not(r,x) ( r.lo = ~x.lo, r.hi = ~x.hi ) -#define INIT(h,l) { h, l } -#define BUILD(r,h,l) ( r.hi = h, r.lo = l ) -#define EXTRACT(h,l,r) ( h = r.hi, l = r.lo ) +#define add(r,x,y) ( r = (x) + (y) ) +#define rorB(r,x,y) ( r = ((x) >> (y)) | ((x) << (64-(y))) ) +#define rorL(r,x,y) ( r = ((x) >> (y)) | ((x) << (64-(y))) ) +#define shrB(r,x,y) ( r = (x) >> (y) ) +#define shrL(r,x,y) ( r = (x) >> (y) ) +#define and(r,x,y) ( r = (x) & (y) ) +#define xor(r,x,y) ( r = (x) ^ (y) ) +#define not(r,x) ( r = ~(x) ) +#define INIT(h,l) ((((uint64_t)(h)) << 32) | (l)) +#define BUILD(r,h,l) ( r = ((((uint64_t)(h)) << 32) | (l)) ) +#define EXTRACT(h,l,r) ( h = (r) >> 32, l = (r) & 0xFFFFFFFFU ) /* ---------------------------------------------------------------------- * Core SHA512 algorithm: processes 16-doubleword blocks into a @@ -48,7 +45,7 @@ shrL(t,x,6), xor(r,r,t) ) static void SHA512_Core_Init(SHA512_State *s) { - static const uint64 iv[] = { + static const uint64_t iv[] = { INIT(0x6a09e667, 0xf3bcc908), INIT(0xbb67ae85, 0x84caa73b), INIT(0x3c6ef372, 0xfe94f82b), @@ -64,7 +61,7 @@ static void SHA512_Core_Init(SHA512_State *s) { } static void SHA384_Core_Init(SHA512_State *s) { - static const uint64 iv[] = { + static const uint64_t iv[] = { INIT(0xcbbb9d5d, 0xc1059ed8), INIT(0x629a292a, 0x367cd507), INIT(0x9159015a, 0x3070dd17), @@ -79,10 +76,10 @@ static void SHA384_Core_Init(SHA512_State *s) { s->h[i] = iv[i]; } -static void SHA512_Block(SHA512_State *s, uint64 *block) { - uint64 w[80]; - uint64 a,b,c,d,e,f,g,h; - static const uint64 k[] = { +static void SHA512_Block(SHA512_State *s, uint64_t *block) { + uint64_t w[80]; + uint64_t a,b,c,d,e,f,g,h; + static const uint64_t k[] = { INIT(0x428a2f98, 0xd728ae22), INIT(0x71374491, 0x23ef65cd), INIT(0xb5c0fbcf, 0xec4d3b2f), INIT(0xe9b5dba5, 0x8189dbbc), INIT(0x3956c25b, 0xf348b538), INIT(0x59f111f1, 0xb605d019), @@ -131,7 +128,7 @@ static void SHA512_Block(SHA512_State *s, uint64 *block) { w[t] = block[t]; for (t = 16; t < 80; t++) { - uint64 p, q, r, tmp; + uint64_t p, q, r, tmp; smallsigma1(p, tmp, w[t-2]); smallsigma0(q, tmp, w[t-15]); add(r, p, q); @@ -143,7 +140,7 @@ static void SHA512_Block(SHA512_State *s, uint64 *block) { e = s->h[4]; f = s->h[5]; g = s->h[6]; h = s->h[7]; for (t = 0; t < 80; t+=8) { - uint64 tmp, p, q, r; + uint64_t tmp, p, q, r; #define ROUND(j,a,b,c,d,e,f,g,h) \ bigsigma1(p, tmp, e); \ @@ -170,7 +167,7 @@ static void SHA512_Block(SHA512_State *s, uint64 *block) { } { - uint64 tmp; + uint64_t tmp; #define UPDATE(state, local) ( tmp = state, add(state, tmp, local) ) UPDATE(s->h[0], a); UPDATE(s->h[1], b); UPDATE(s->h[2], c); UPDATE(s->h[3], d); @@ -185,35 +182,36 @@ static void SHA512_Block(SHA512_State *s, uint64 *block) { * at the end, and pass those blocks to the core SHA512 algorithm. */ +static void SHA512_BinarySink_write(BinarySink *bs, + const void *p, size_t len); + void SHA512_Init(SHA512_State *s) { - int i; SHA512_Core_Init(s); s->blkused = 0; - for (i = 0; i < 4; i++) - s->len[i] = 0; + s->lenhi = s->lenlo = 0; + BinarySink_INIT(s, SHA512_BinarySink_write); } void SHA384_Init(SHA512_State *s) { - int i; SHA384_Core_Init(s); s->blkused = 0; - for (i = 0; i < 4; i++) - s->len[i] = 0; + s->lenhi = s->lenlo = 0; + BinarySink_INIT(s, SHA512_BinarySink_write); } -void SHA512_Bytes(SHA512_State *s, const void *p, int len) { +static void SHA512_BinarySink_write(BinarySink *bs, + const void *p, size_t len) +{ + SHA512_State *s = BinarySink_DOWNCAST(bs, SHA512_State); unsigned char *q = (unsigned char *)p; - uint64 wordblock[16]; - uint32 lenw = len; + uint64_t wordblock[16]; int i; /* * Update the length field. */ - for (i = 0; i < 4; i++) { - s->len[i] += lenw; - lenw = (s->len[i] < lenw); - } + s->lenlo += len; + s->lenhi += (s->lenlo < len); if (s->blkused && s->blkused+len < BLKSIZE) { /* @@ -230,18 +228,8 @@ void SHA512_Bytes(SHA512_State *s, const void *p, int len) { q += BLKSIZE - s->blkused; len -= BLKSIZE - s->blkused; /* Now process the block. Gather bytes big-endian into words */ - for (i = 0; i < 16; i++) { - uint32 h, l; - h = ( ((uint32)s->block[i*8+0]) << 24 ) | - ( ((uint32)s->block[i*8+1]) << 16 ) | - ( ((uint32)s->block[i*8+2]) << 8 ) | - ( ((uint32)s->block[i*8+3]) << 0 ); - l = ( ((uint32)s->block[i*8+4]) << 24 ) | - ( ((uint32)s->block[i*8+5]) << 16 ) | - ( ((uint32)s->block[i*8+6]) << 8 ) | - ( ((uint32)s->block[i*8+7]) << 0 ); - BUILD(wordblock[i], h, l); - } + for (i = 0; i < 16; i++) + wordblock[i] = GET_64BIT_MSB_FIRST(s->block + i*8); SHA512_Block(s, wordblock); s->blkused = 0; } @@ -254,44 +242,25 @@ void SHA512_Final(SHA512_State *s, unsigned char *digest) { int i; int pad; unsigned char c[BLKSIZE]; - uint32 len[4]; + uint64_t lenhi, lenlo; if (s->blkused >= BLKSIZE-16) pad = (BLKSIZE-16) + BLKSIZE - s->blkused; else pad = (BLKSIZE-16) - s->blkused; - for (i = 4; i-- ;) { - uint32 lenhi = s->len[i]; - uint32 lenlo = i > 0 ? s->len[i-1] : 0; - len[i] = (lenhi << 3) | (lenlo >> (32-3)); - } + lenhi = (s->lenhi << 3) | (s->lenlo >> (32-3)); + lenlo = (s->lenlo << 3); memset(c, 0, pad); c[0] = 0x80; - SHA512_Bytes(s, &c, pad); + put_data(s, &c, pad); - for (i = 0; i < 4; i++) { - c[i*4+0] = (len[3-i] >> 24) & 0xFF; - c[i*4+1] = (len[3-i] >> 16) & 0xFF; - c[i*4+2] = (len[3-i] >> 8) & 0xFF; - c[i*4+3] = (len[3-i] >> 0) & 0xFF; - } + put_uint64(s, lenhi); + put_uint64(s, lenlo); - SHA512_Bytes(s, &c, 16); - - for (i = 0; i < 8; i++) { - uint32 h, l; - EXTRACT(h, l, s->h[i]); - digest[i*8+0] = (h >> 24) & 0xFF; - digest[i*8+1] = (h >> 16) & 0xFF; - digest[i*8+2] = (h >> 8) & 0xFF; - digest[i*8+3] = (h >> 0) & 0xFF; - digest[i*8+4] = (l >> 24) & 0xFF; - digest[i*8+5] = (l >> 16) & 0xFF; - digest[i*8+6] = (l >> 8) & 0xFF; - digest[i*8+7] = (l >> 0) & 0xFF; - } + for (i = 0; i < 8; i++) + PUT_64BIT_MSB_FIRST(digest + i*8, s->h[i]); } void SHA384_Final(SHA512_State *s, unsigned char *digest) { @@ -304,7 +273,7 @@ void SHA512_Simple(const void *p, int len, unsigned char *output) { SHA512_State s; SHA512_Init(&s); - SHA512_Bytes(&s, p, len); + put_data(&s, p, len); SHA512_Final(&s, output); smemclr(&s, sizeof(s)); } @@ -313,7 +282,7 @@ void SHA384_Simple(const void *p, int len, unsigned char *output) { SHA512_State s; SHA384_Init(&s); - SHA512_Bytes(&s, p, len); + put_data(&s, p, len); SHA384_Final(&s, output); smemclr(&s, sizeof(s)); } @@ -322,74 +291,71 @@ void SHA384_Simple(const void *p, int len, unsigned char *output) { * Thin abstraction for things where hashes are pluggable. */ -static void *sha512_init(void) -{ - SHA512_State *s; +struct sha512_hash { + SHA512_State state; + ssh_hash hash; +}; - s = snew(SHA512_State); - SHA512_Init(s); - return s; +static ssh_hash *sha512_new(const struct ssh_hashalg *alg) +{ + struct sha512_hash *h = snew(struct sha512_hash); + SHA512_Init(&h->state); + h->hash.vt = alg; + BinarySink_DELEGATE_INIT(&h->hash, &h->state); + return &h->hash; } -static void *sha512_copy(const void *vold) +static ssh_hash *sha512_copy(ssh_hash *hashold) { - const SHA512_State *old = (const SHA512_State *)vold; - SHA512_State *s; + struct sha512_hash *hold, *hnew; + ssh_hash *hashnew = sha512_new(hashold->vt); - s = snew(SHA512_State); - *s = *old; - return s; -} + hold = container_of(hashold, struct sha512_hash, hash); + hnew = container_of(hashnew, struct sha512_hash, hash); -static void sha512_free(void *handle) -{ - SHA512_State *s = handle; + hnew->state = hold->state; + BinarySink_COPIED(&hnew->state); - smemclr(s, sizeof(*s)); - sfree(s); + return hashnew; } -static void sha512_bytes(void *handle, const void *p, int len) +static void sha512_free(ssh_hash *hash) { - SHA512_State *s = handle; + struct sha512_hash *h = container_of(hash, struct sha512_hash, hash); - SHA512_Bytes(s, p, len); + smemclr(h, sizeof(*h)); + sfree(h); } -static void sha512_final(void *handle, unsigned char *output) +static void sha512_final(ssh_hash *hash, unsigned char *output) { - SHA512_State *s = handle; - - SHA512_Final(s, output); - sha512_free(s); + struct sha512_hash *h = container_of(hash, struct sha512_hash, hash); + SHA512_Final(&h->state, output); + sha512_free(hash); } -const struct ssh_hash ssh_sha512 = { - sha512_init, sha512_copy, sha512_bytes, sha512_final, sha512_free, - 64, "SHA-512" +const struct ssh_hashalg ssh_sha512 = { + sha512_new, sha512_copy, sha512_final, sha512_free, 64, "SHA-512" }; -static void *sha384_init(void) +static ssh_hash *sha384_new(const struct ssh_hashalg *alg) { - SHA512_State *s; - - s = snew(SHA512_State); - SHA384_Init(s); - return s; + struct sha512_hash *h = snew(struct sha512_hash); + SHA384_Init(&h->state); + h->hash.vt = alg; + BinarySink_DELEGATE_INIT(&h->hash, &h->state); + return &h->hash; } -static void sha384_final(void *handle, unsigned char *output) +static void sha384_final(ssh_hash *hash, unsigned char *output) { - SHA512_State *s = handle; - - SHA384_Final(s, output); - smemclr(s, sizeof(*s)); - sfree(s); + struct sha512_hash *h = container_of(hash, struct sha512_hash, hash); + SHA384_Final(&h->state, output); + sha512_free(hash); } -const struct ssh_hash ssh_sha384 = { - sha384_init, sha512_copy, sha512_bytes, sha384_final, sha512_free, - 48, "SHA-384" +const struct ssh_hashalg ssh_sha384 = { + sha384_new, sha512_copy, sha384_final, sha512_free, 48, "SHA-384" }; #ifdef TEST @@ -450,8 +416,7 @@ int main(void) { int n; SHA512_Init(&s); for (n = 0; n < 1000000 / 40; n++) - SHA512_Bytes(&s, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - 40); + put_data(&s, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 40); SHA512_Final(&s, digest); } for (j = 0; j < 64; j++) { diff --git a/sshsha.c b/sshsha.c index c10a8217..66d9aca5 100644 --- a/sshsha.c +++ b/sshsha.c @@ -7,13 +7,18 @@ #include "ssh.h" +#include + /* ---------------------------------------------------------------------- * Core SHA algorithm: processes 16-word blocks into a message digest. */ -#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) ) +#define rol(x,y) ( ((x) << (y)) | (((uint32_t)x) >> (32-y)) ) + +static void sha1_sw(SHA_State * s, const unsigned char *q, int len); +static void sha1_ni(SHA_State * s, const unsigned char *q, int len); -static void SHA_Core_Init(uint32 h[5]) +static void SHA_Core_Init(uint32_t h[5]) { h[0] = 0x67452301; h[1] = 0xefcdab89; @@ -22,10 +27,10 @@ static void SHA_Core_Init(uint32 h[5]) h[4] = 0xc3d2e1f0; } -void SHATransform(word32 * digest, word32 * block) +void SHATransform(uint32_t * digest, uint32_t * block) { - word32 w[80]; - word32 a, b, c, d, e; + uint32_t w[80]; + uint32_t a, b, c, d, e; int t; #ifdef RANDOM_DIAGNOSTICS @@ -47,7 +52,7 @@ void SHATransform(word32 * digest, word32 * block) w[t] = block[t]; for (t = 16; t < 80; t++) { - word32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; + uint32_t tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]; w[t] = rol(tmp, 1); } @@ -58,7 +63,7 @@ void SHATransform(word32 * digest, word32 * block) e = digest[4]; for (t = 0; t < 20; t++) { - word32 tmp = + uint32_t tmp = rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999; e = d; d = c; @@ -67,7 +72,7 @@ void SHATransform(word32 * digest, word32 * block) a = tmp; } for (t = 20; t < 40; t++) { - word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1; + uint32_t tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1; e = d; d = c; c = rol(b, 30); @@ -75,7 +80,7 @@ void SHATransform(word32 * digest, word32 * block) a = tmp; } for (t = 40; t < 60; t++) { - word32 tmp = rol(a, + uint32_t tmp = rol(a, 5) + ((b & c) | (b & d) | (c & d)) + e + w[t] + 0x8f1bbcdc; e = d; @@ -85,7 +90,7 @@ void SHATransform(word32 * digest, word32 * block) a = tmp; } for (t = 60; t < 80; t++) { - word32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6; + uint32_t tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6; e = d; d = c; c = rol(b, 30); @@ -119,25 +124,37 @@ void SHATransform(word32 * digest, word32 * block) * the end, and pass those blocks to the core SHA algorithm. */ +static void SHA_BinarySink_write(BinarySink *bs, const void *p, size_t len); + void SHA_Init(SHA_State * s) { SHA_Core_Init(s->h); s->blkused = 0; - s->lenhi = s->lenlo = 0; + s->len = 0; + if (supports_sha_ni()) + s->sha1 = &sha1_ni; + else + s->sha1 = &sha1_sw; + BinarySink_INIT(s, SHA_BinarySink_write); } -void SHA_Bytes(SHA_State * s, const void *p, int len) +static void SHA_BinarySink_write(BinarySink *bs, const void *p, size_t len) { + struct SHA_State *s = BinarySink_DOWNCAST(bs, struct SHA_State); const unsigned char *q = (const unsigned char *) p; - uint32 wordblock[16]; - uint32 lenw = len; - int i; /* * Update the length field. */ - s->lenlo += lenw; - s->lenhi += (s->lenlo < lenw); + s->len += len; + + (*(s->sha1))(s, q, len); +} + +static void sha1_sw(SHA_State * s, const unsigned char *q, int len) +{ + uint32_t wordblock[16]; + int i; if (s->blkused && s->blkused + len < 64) { /* @@ -156,10 +173,10 @@ void SHA_Bytes(SHA_State * s, const void *p, int len) /* Now process the block. Gather bytes big-endian into words */ for (i = 0; i < 16; i++) { wordblock[i] = - (((uint32) s->block[i * 4 + 0]) << 24) | - (((uint32) s->block[i * 4 + 1]) << 16) | - (((uint32) s->block[i * 4 + 2]) << 8) | - (((uint32) s->block[i * 4 + 3]) << 0); + (((uint32_t) s->block[i * 4 + 0]) << 24) | + (((uint32_t) s->block[i * 4 + 1]) << 16) | + (((uint32_t) s->block[i * 4 + 2]) << 8) | + (((uint32_t) s->block[i * 4 + 3]) << 0); } SHATransform(s->h, wordblock); s->blkused = 0; @@ -174,30 +191,20 @@ void SHA_Final(SHA_State * s, unsigned char *output) int i; int pad; unsigned char c[64]; - uint32 lenhi, lenlo; + uint64_t len; if (s->blkused >= 56) pad = 56 + 64 - s->blkused; else pad = 56 - s->blkused; - lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3)); - lenlo = (s->lenlo << 3); + len = (s->len << 3); memset(c, 0, pad); c[0] = 0x80; - SHA_Bytes(s, &c, pad); + put_data(s, &c, pad); - c[0] = (lenhi >> 24) & 0xFF; - c[1] = (lenhi >> 16) & 0xFF; - c[2] = (lenhi >> 8) & 0xFF; - c[3] = (lenhi >> 0) & 0xFF; - c[4] = (lenlo >> 24) & 0xFF; - c[5] = (lenlo >> 16) & 0xFF; - c[6] = (lenlo >> 8) & 0xFF; - c[7] = (lenlo >> 0) & 0xFF; - - SHA_Bytes(s, &c, 8); + put_uint64(s, len); for (i = 0; i < 5; i++) { output[i * 4] = (s->h[i] >> 24) & 0xFF; @@ -212,7 +219,7 @@ void SHA_Simple(const void *p, int len, unsigned char *output) SHA_State s; SHA_Init(&s); - SHA_Bytes(&s, p, len); + put_data(&s, p, len); SHA_Final(&s, output); smemclr(&s, sizeof(s)); } @@ -221,50 +228,51 @@ void SHA_Simple(const void *p, int len, unsigned char *output) * Thin abstraction for things where hashes are pluggable. */ -static void *sha1_init(void) -{ - SHA_State *s; +struct sha1_hash { + SHA_State state; + ssh_hash hash; +}; - s = snew(SHA_State); - SHA_Init(s); - return s; +static ssh_hash *sha1_new(const struct ssh_hashalg *alg) +{ + struct sha1_hash *h = snew(struct sha1_hash); + SHA_Init(&h->state); + h->hash.vt = alg; + BinarySink_DELEGATE_INIT(&h->hash, &h->state); + return &h->hash; } -static void *sha1_copy(const void *vold) +static ssh_hash *sha1_copy(ssh_hash *hashold) { - const SHA_State *old = (const SHA_State *)vold; - SHA_State *s; + struct sha1_hash *hold, *hnew; + ssh_hash *hashnew = sha1_new(hashold->vt); - s = snew(SHA_State); - *s = *old; - return s; -} + hold = container_of(hashold, struct sha1_hash, hash); + hnew = container_of(hashnew, struct sha1_hash, hash); -static void sha1_free(void *handle) -{ - SHA_State *s = handle; + hnew->state = hold->state; + BinarySink_COPIED(&hnew->state); - smemclr(s, sizeof(*s)); - sfree(s); + return hashnew; } -static void sha1_bytes(void *handle, const void *p, int len) +static void sha1_free(ssh_hash *hash) { - SHA_State *s = handle; + struct sha1_hash *h = container_of(hash, struct sha1_hash, hash); - SHA_Bytes(s, p, len); + smemclr(h, sizeof(*h)); + sfree(h); } -static void sha1_final(void *handle, unsigned char *output) +static void sha1_final(ssh_hash *hash, unsigned char *output) { - SHA_State *s = handle; - - SHA_Final(s, output); - sha1_free(s); + struct sha1_hash *h = container_of(hash, struct sha1_hash, hash); + SHA_Final(&h->state, output); + sha1_free(hash); } -const struct ssh_hash ssh_sha1 = { - sha1_init, sha1_copy, sha1_bytes, sha1_final, sha1_free, 20, "SHA-1" +const struct ssh_hashalg ssh_sha1 = { + sha1_new, sha1_copy, sha1_final, sha1_free, 20, "SHA-1" }; /* ---------------------------------------------------------------------- @@ -272,20 +280,30 @@ const struct ssh_hash ssh_sha1 = { * HMAC wrapper on it. */ -static void *sha1_make_context(void *cipher_ctx) +struct hmacsha1 { + SHA_State sha[3]; + ssh2_mac mac; +}; + +static ssh2_mac *hmacsha1_new( + const struct ssh2_macalg *alg, ssh2_cipher *cipher) { - return snewn(3, SHA_State); + struct hmacsha1 *ctx = snew(struct hmacsha1); + ctx->mac.vt = alg; + BinarySink_DELEGATE_INIT(&ctx->mac, &ctx->sha[2]); + return &ctx->mac; } -static void sha1_free_context(void *handle) +static void hmacsha1_free(ssh2_mac *mac) { - smemclr(handle, 3 * sizeof(SHA_State)); - sfree(handle); + struct hmacsha1 *ctx = container_of(mac, struct hmacsha1, mac); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); } -static void sha1_key_internal(void *handle, unsigned char *key, int len) +static void sha1_key_internal(SHA_State *keys, + const unsigned char *key, int len) { - SHA_State *keys = (SHA_State *)handle; unsigned char foo[64]; int i; @@ -293,163 +311,384 @@ static void sha1_key_internal(void *handle, unsigned char *key, int len) for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; SHA_Init(&keys[0]); - SHA_Bytes(&keys[0], foo, 64); + put_data(&keys[0], foo, 64); memset(foo, 0x5C, 64); for (i = 0; i < len && i < 64; i++) foo[i] ^= key[i]; SHA_Init(&keys[1]); - SHA_Bytes(&keys[1], foo, 64); + put_data(&keys[1], foo, 64); smemclr(foo, 64); /* burn the evidence */ } -static void sha1_key(void *handle, unsigned char *key) +static void hmacsha1_key(ssh2_mac *mac, const void *key) { - sha1_key_internal(handle, key, 20); + struct hmacsha1 *ctx = container_of(mac, struct hmacsha1, mac); + /* Reading the key length out of the ssh2_macalg structure means + * this same method can be used for the _buggy variants which use + * a shorter key */ + sha1_key_internal(ctx->sha, key, ctx->mac.vt->keylen); } -static void sha1_key_buggy(void *handle, unsigned char *key) +static void hmacsha1_start(ssh2_mac *mac) { - sha1_key_internal(handle, key, 16); -} - -static void hmacsha1_start(void *handle) -{ - SHA_State *keys = (SHA_State *)handle; + struct hmacsha1 *ctx = container_of(mac, struct hmacsha1, mac); - keys[2] = keys[0]; /* structure copy */ -} - -static void hmacsha1_bytes(void *handle, unsigned char const *blk, int len) -{ - SHA_State *keys = (SHA_State *)handle; - SHA_Bytes(&keys[2], (void *)blk, len); + ctx->sha[2] = ctx->sha[0]; /* structure copy */ + BinarySink_COPIED(&ctx->sha[2]); } -static void hmacsha1_genresult(void *handle, unsigned char *hmac) +static void hmacsha1_genresult(ssh2_mac *mac, unsigned char *hmac) { - SHA_State *keys = (SHA_State *)handle; + struct hmacsha1 *ctx = container_of(mac, struct hmacsha1, mac); SHA_State s; unsigned char intermediate[20]; - s = keys[2]; /* structure copy */ + s = ctx->sha[2]; /* structure copy */ + BinarySink_COPIED(&s); SHA_Final(&s, intermediate); - s = keys[1]; /* structure copy */ - SHA_Bytes(&s, intermediate, 20); - SHA_Final(&s, hmac); -} - -static void sha1_do_hmac(void *handle, unsigned char *blk, int len, - unsigned long seq, unsigned char *hmac) -{ - unsigned char seqbuf[4]; - - PUT_32BIT_MSB_FIRST(seqbuf, seq); - hmacsha1_start(handle); - hmacsha1_bytes(handle, seqbuf, 4); - hmacsha1_bytes(handle, blk, len); - hmacsha1_genresult(handle, hmac); -} - -static void sha1_generate(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - sha1_do_hmac(handle, blk, len, seq, blk + len); -} - -static int hmacsha1_verresult(void *handle, unsigned char const *hmac) -{ - unsigned char correct[20]; - hmacsha1_genresult(handle, correct); - return smemeq(correct, hmac, 20); -} - -static int sha1_verify(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - unsigned char correct[20]; - sha1_do_hmac(handle, blk, len, seq, correct); - return smemeq(correct, blk + len, 20); -} - -static void hmacsha1_96_genresult(void *handle, unsigned char *hmac) -{ - unsigned char full[20]; - hmacsha1_genresult(handle, full); - memcpy(hmac, full, 12); -} - -static void sha1_96_generate(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - unsigned char full[20]; - sha1_do_hmac(handle, blk, len, seq, full); - memcpy(blk + len, full, 12); -} - -static int hmacsha1_96_verresult(void *handle, unsigned char const *hmac) -{ - unsigned char correct[20]; - hmacsha1_genresult(handle, correct); - return smemeq(correct, hmac, 12); -} - -static int sha1_96_verify(void *handle, unsigned char *blk, int len, - unsigned long seq) -{ - unsigned char correct[20]; - sha1_do_hmac(handle, blk, len, seq, correct); - return smemeq(correct, blk + len, 12); + s = ctx->sha[1]; /* structure copy */ + BinarySink_COPIED(&s); + put_data(&s, intermediate, 20); + SHA_Final(&s, intermediate); + memcpy(hmac, intermediate, ctx->mac.vt->len); + smemclr(intermediate, sizeof(intermediate)); } -void hmac_sha1_simple(void *key, int keylen, void *data, int datalen, +void hmac_sha1_simple(const void *key, int keylen, + const void *data, int datalen, unsigned char *output) { SHA_State states[2]; unsigned char intermediate[20]; sha1_key_internal(states, key, keylen); - SHA_Bytes(&states[0], data, datalen); + put_data(&states[0], data, datalen); SHA_Final(&states[0], intermediate); - SHA_Bytes(&states[1], intermediate, 20); + put_data(&states[1], intermediate, 20); SHA_Final(&states[1], output); } -const struct ssh_mac ssh_hmac_sha1 = { - sha1_make_context, sha1_free_context, sha1_key, - sha1_generate, sha1_verify, - hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult, +const struct ssh2_macalg ssh_hmac_sha1 = { + hmacsha1_new, hmacsha1_free, hmacsha1_key, + hmacsha1_start, hmacsha1_genresult, "hmac-sha1", "hmac-sha1-etm@openssh.com", 20, 20, "HMAC-SHA1" }; -const struct ssh_mac ssh_hmac_sha1_96 = { - sha1_make_context, sha1_free_context, sha1_key, - sha1_96_generate, sha1_96_verify, - hmacsha1_start, hmacsha1_bytes, - hmacsha1_96_genresult, hmacsha1_96_verresult, +const struct ssh2_macalg ssh_hmac_sha1_96 = { + hmacsha1_new, hmacsha1_free, hmacsha1_key, + hmacsha1_start, hmacsha1_genresult, "hmac-sha1-96", "hmac-sha1-96-etm@openssh.com", 12, 20, "HMAC-SHA1-96" }; -const struct ssh_mac ssh_hmac_sha1_buggy = { - sha1_make_context, sha1_free_context, sha1_key_buggy, - sha1_generate, sha1_verify, - hmacsha1_start, hmacsha1_bytes, hmacsha1_genresult, hmacsha1_verresult, +const struct ssh2_macalg ssh_hmac_sha1_buggy = { + hmacsha1_new, hmacsha1_free, hmacsha1_key, + hmacsha1_start, hmacsha1_genresult, "hmac-sha1", NULL, 20, 16, "bug-compatible HMAC-SHA1" }; -const struct ssh_mac ssh_hmac_sha1_96_buggy = { - sha1_make_context, sha1_free_context, sha1_key_buggy, - sha1_96_generate, sha1_96_verify, - hmacsha1_start, hmacsha1_bytes, - hmacsha1_96_genresult, hmacsha1_96_verresult, +const struct ssh2_macalg ssh_hmac_sha1_96_buggy = { + hmacsha1_new, hmacsha1_free, hmacsha1_key, + hmacsha1_start, hmacsha1_genresult, "hmac-sha1-96", NULL, 12, 16, "bug-compatible HMAC-SHA1-96" }; + +#ifdef COMPILER_SUPPORTS_SHA_NI + +#if defined _MSC_VER && defined _M_AMD64 +# include +#endif + +/* + * Set target architecture for Clang and GCC + */ +#if !defined(__clang__) && defined(__GNUC__) +# pragma GCC target("sha") +# pragma GCC target("sse4.1") +#endif + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 5)) +# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) +#else +# define FUNC_ISA +#endif + +#include +#include +#include + +#if defined(__clang__) || defined(__GNUC__) +#include +#endif + +/* + * Determinators of CPU type + */ +#if defined(__clang__) || defined(__GNUC__) + +#include +bool supports_sha_ni(void) +{ + unsigned int CPUInfo[4]; + __cpuid(0, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); + if (CPUInfo[0] < 7) + return false; + + __cpuid_count(7, 0, CPUInfo[0], CPUInfo[1], CPUInfo[2], CPUInfo[3]); + return CPUInfo[1] & (1 << 29); /* SHA */ +} + +#else /* defined(__clang__) || defined(__GNUC__) */ + +bool supports_sha_ni(void) +{ + unsigned int CPUInfo[4]; + __cpuid(CPUInfo, 0); + if (CPUInfo[0] < 7) + return false; + + __cpuidex(CPUInfo, 7, 0); + return CPUInfo[1] & (1 << 29); /* Check SHA */ +} + +#endif /* defined(__clang__) || defined(__GNUC__) */ + +/* SHA1 implementation using new instructions + The code is based on Jeffrey Walton's SHA1 implementation: + https://github.com/noloader/SHA-Intrinsics +*/ +FUNC_ISA +static void sha1_ni_(SHA_State * s, const unsigned char *q, int len) +{ + if (s->blkused && s->blkused + len < 64) { + /* + * Trivial case: just add to the block. + */ + memcpy(s->block + s->blkused, q, len); + s->blkused += len; + } else { + __m128i ABCD, ABCD_SAVE, E0, E0_SAVE, E1; + const __m128i MASK = _mm_set_epi64x(0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); + + ABCD = _mm_loadu_si128((const __m128i*) s->h); + E0 = _mm_set_epi32(s->h[4], 0, 0, 0); + ABCD = _mm_shuffle_epi32(ABCD, 0x1B); + + /* + * We must complete and process at least one block. + */ + while (s->blkused + len >= 64) + { + __m128i MSG0, MSG1, MSG2, MSG3; + memcpy(s->block + s->blkused, q, 64 - s->blkused); + q += 64 - s->blkused; + len -= 64 - s->blkused; + + /* Save current state */ + ABCD_SAVE = ABCD; + E0_SAVE = E0; + + /* Rounds 0-3 */ + MSG0 = _mm_loadu_si128((const __m128i*)(s->block + 0)); + 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((const __m128i*)(s->block + 16)); + 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((const __m128i*)(s->block + 32)); + 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((const __m128i*)(s->block + 48)); + 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 */ + E0 = _mm_sha1nexte_epu32(E0, E0_SAVE); + ABCD = _mm_add_epi32(ABCD, ABCD_SAVE); + + s->blkused = 0; + } + + ABCD = _mm_shuffle_epi32(ABCD, 0x1B); + + /* Save state */ + _mm_storeu_si128((__m128i*) s->h, ABCD); + s->h[4] = _mm_extract_epi32(E0, 3); + + memcpy(s->block, q, len); + s->blkused = len; + } +} + +/* + * Workaround LLVM bug https://bugs.llvm.org/show_bug.cgi?id=34980 + */ +static void sha1_ni(SHA_State * s, const unsigned char *q, int len) +{ + sha1_ni_(s, q, len); +} + +#else /* COMPILER_SUPPORTS_AES_NI */ + +static void sha1_ni(SHA_State * s, const unsigned char *q, int len) +{ + assert(0); +} + +bool supports_sha_ni(void) +{ + return false; +} + +#endif /* COMPILER_SUPPORTS_AES_NI */ diff --git a/sshshare.c b/sshshare.c index 82c4bd31..664a622e 100644 --- a/sshshare.c +++ b/sshshare.c @@ -140,31 +140,28 @@ #include "ssh.h" struct ssh_sharing_state { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - char *sockname; /* the socket name, kept for cleanup */ - Socket listensock; /* the master listening Socket */ + Socket *listensock; /* the master listening Socket */ tree234 *connections; /* holds ssh_sharing_connstates */ unsigned nextid; /* preferred id for next connstate */ - Ssh ssh; /* instance of the ssh backend */ + 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 { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - unsigned id; /* used to identify this downstream in log messages */ - Socket sock; /* the Socket for this connection */ + Socket *sock; /* the Socket for this connection */ struct ssh_sharing_state *parent; int crLine; /* coroutine state for share_receive */ - int sent_verstring, got_verstring, curr_packetlen; + bool sent_verstring, got_verstring; + int curr_packetlen; unsigned char recvbuf[0x4010]; int recvlen; @@ -203,6 +200,8 @@ struct ssh_sharing_connstate { /* Global requests we've sent on to the server, pending replies. */ struct share_globreq *globreq_head, *globreq_tail; + + Plug plug; }; struct share_halfchannel { @@ -237,13 +236,14 @@ struct share_channel { int x11_auth_proto; char *x11_auth_data; int x11_auth_datalen; - int x11_one_shot; + bool x11_one_shot; }; struct share_forwarding { char *host; int port; - int active; /* has the server sent REQUEST_SUCCESS? */ + bool active; /* has the server sent REQUEST_SUCCESS? */ + struct ssh_rportfwd *rpf; }; struct share_xchannel_message { @@ -264,7 +264,7 @@ struct share_xchannel { * channel messages from the server until such time as the server * sends us CHANNEL_CLOSE. */ - int live; + bool live; /* * When we receive OPEN_CONFIRMATION, we will need to send a @@ -292,7 +292,7 @@ enum { struct share_globreq { struct share_globreq *next; int type; - int want_reply; + bool want_reply; struct share_forwarding *fwd; }; @@ -509,9 +509,8 @@ static void share_connstate_free(struct ssh_sharing_connstate *cs) sfree(cs); } -void sharestate_free(void *v) +void sharestate_free(ssh_sharing_state *sharestate) { - struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)v; struct ssh_sharing_connstate *cs; platform_ssh_share_cleanup(sharestate->sockname); @@ -573,7 +572,7 @@ static struct share_channel *share_add_channel chan->x11_auth_data = NULL; chan->x11_auth_proto = -1; chan->x11_auth_datalen = 0; - chan->x11_one_shot = 0; + chan->x11_one_shot = false; if (add234(cs->channels_by_us, chan) != chan) { sfree(chan); return NULL; @@ -620,7 +619,7 @@ static void share_remove_channel(struct ssh_sharing_connstate *cs, del234(cs->channels_by_us, chan); del234(cs->channels_by_server, chan); if (chan->x11_auth_upstream) - ssh_sharing_remove_x11_display(cs->parent->ssh, + ssh_remove_sharing_x11_display(cs->parent->cl, chan->x11_auth_upstream); sfree(chan->x11_auth_data); sfree(chan); @@ -633,7 +632,7 @@ static struct share_xchannel *share_add_xchannel struct share_xchannel *xc = snew(struct share_xchannel); xc->upstream_id = upstream_id; xc->server_id = server_id; - xc->live = TRUE; + xc->live = true; xc->msghead = xc->msgtail = NULL; if (add234(cs->xchannels_by_us, xc) != xc) { sfree(xc); @@ -678,7 +677,7 @@ static struct share_forwarding *share_add_forwarding struct share_forwarding *fwd = snew(struct share_forwarding); fwd->host = dupstr(host); fwd->port = port; - fwd->active = FALSE; + fwd->active = false; if (add234(cs->forwardings, fwd) != fwd) { /* Duplicate?! */ sfree(fwd); @@ -705,10 +704,39 @@ static void share_remove_forwarding(struct ssh_sharing_connstate *cs, sfree(fwd); } +static 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 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; @@ -728,37 +756,41 @@ static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, * If that happens, we just chop up the packet into pieces and * send them as separate CHANNEL_DATA packets. */ - const char *upkt = (const char *)pkt; - char header[13]; /* 4 length + 1 type + 4 channel id + 4 string len */ - - int len = toint(GET_32BIT(upkt + 4)); - upkt += 8; /* skip channel id + length field */ + BinarySource src[1]; + unsigned channel; + ptrlen data; - if (len < 0 || len > pktlen - 8) - len = pktlen - 8; + BinarySource_BARE_INIT(src, pkt, pktlen); + channel = get_uint32(src); + data = get_string(src); do { - int this_len = (len > chan->downstream_maxpkt ? - chan->downstream_maxpkt : len); - PUT_32BIT(header, this_len + 9); - header[4] = type; - PUT_32BIT(header + 5, chan->downstream_id); - PUT_32BIT(header + 9, this_len); - sk_write(cs->sock, header, 13); - sk_write(cs->sock, upkt, this_len); - len -= this_len; - upkt += this_len; - } while (len > 0); + int this_len = (data.len > chan->downstream_maxpkt ? + chan->downstream_maxpkt : data.len); + + packet = strbuf_new(); + 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(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. */ - char header[9]; - - PUT_32BIT(header, pktlen + 1); - header[4] = type; - sk_write(cs->sock, header, 5); - sk_write(cs->sock, pkt, pktlen); + packet = strbuf_new(); + put_uint32(packet, 0); /* placeholder for length field */ + put_byte(packet, type); + put_data(packet, pkt, pktlen); + PUT_32BIT(packet->s, packet->len-4); + sk_write(cs->sock, packet->s, packet->len); + strbuf_free(packet); } } @@ -778,19 +810,18 @@ static void share_try_cleanup(struct ssh_sharing_connstate *cs) index234(cs->halfchannels, 0)) != NULL) { static const char reason[] = "PuTTY downstream no longer available"; static const char lang[] = "en"; - unsigned char packet[256]; - int pos = 0; - - PUT_32BIT(packet + pos, hc->server_id); pos += 4; - PUT_32BIT(packet + pos, SSH2_OPEN_CONNECT_FAILED); pos += 4; - PUT_32BIT(packet + pos, strlen(reason)); pos += 4; - memcpy(packet + pos, reason, strlen(reason)); pos += strlen(reason); - PUT_32BIT(packet + pos, strlen(lang)); pos += 4; - memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang); - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, - SSH2_MSG_CHANNEL_OPEN_FAILURE, - packet, pos, "cleanup after" - " downstream went away"); + 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); } @@ -810,20 +841,22 @@ static void share_try_cleanup(struct ssh_sharing_connstate *cs) */ for (i = 0; (chan = (struct share_channel *) index234(cs->channels_by_us, i)) != NULL; i++) { - unsigned char packet[256]; - int pos = 0; + strbuf *packet; if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { - PUT_32BIT(packet + pos, chan->server_id); pos += 4; - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, - SSH2_MSG_CHANNEL_CLOSE, - packet, pos, "cleanup after" - " downstream went away"); + 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->ssh, chan->upstream_id); + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); share_remove_channel(cs, chan); i--; /* don't accidentally skip one as a result */ } @@ -841,28 +874,18 @@ static void share_try_cleanup(struct ssh_sharing_connstate *cs) for (i = 0; (fwd = (struct share_forwarding *) index234(cs->forwardings, i)) != NULL; i++) { if (fwd->active) { - static const char request[] = "cancel-tcpip-forward"; - char *packet = snewn(256 + strlen(fwd->host), char); - int pos = 0; - - PUT_32BIT(packet + pos, strlen(request)); pos += 4; - memcpy(packet + pos, request, strlen(request)); - pos += strlen(request); - - packet[pos++] = 0; /* !want_reply */ - - PUT_32BIT(packet + pos, strlen(fwd->host)); pos += 4; - memcpy(packet + pos, fwd->host, strlen(fwd->host)); - pos += strlen(fwd->host); - - PUT_32BIT(packet + pos, fwd->port); pos += 4; - - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, - SSH2_MSG_GLOBAL_REQUEST, - packet, pos, "cleanup after" - " downstream went away"); - sfree(packet); - + 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 */ } @@ -871,12 +894,22 @@ static void share_try_cleanup(struct ssh_sharing_connstate *cs) 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(cs->parent->connections, cs); - ssh_sharing_downstream_disconnected(cs->parent->ssh, cs->id); + 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); } } @@ -892,29 +925,22 @@ static void share_begin_cleanup(struct ssh_sharing_connstate *cs) static void share_disconnect(struct ssh_sharing_connstate *cs, const char *message) { - static const char lang[] = "en"; - int msglen = strlen(message); - char *packet = snewn(msglen + 256, char); - int pos = 0; - - PUT_32BIT(packet + pos, SSH2_DISCONNECT_PROTOCOL_ERROR); pos += 4; - - PUT_32BIT(packet + pos, msglen); pos += 4; - memcpy(packet + pos, message, msglen); - pos += msglen; - - PUT_32BIT(packet + pos, strlen(lang)); pos += 4; - memcpy(packet + pos, lang, strlen(lang)); pos += strlen(lang); - - send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, packet, pos, NULL); + 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 int share_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void share_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { - struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; + struct ssh_sharing_connstate *cs = container_of( + plug, struct ssh_sharing_connstate, plug); if (error_msg) { #ifdef BROKEN_PIPE_ERROR_CODE @@ -931,72 +957,28 @@ static int share_closing(Plug plug, const char *error_msg, int error_code, /* do nothing */; else #endif - ssh_sharing_logf(cs->parent->ssh, cs->id, - "Socket error: %s", error_msg); + log_downstream(cs, "Socket error: %s", error_msg); } share_begin_cleanup(cs); - return 1; -} - -static int getstring_inner(const void *vdata, int datalen, - char **out, int *outlen) -{ - const unsigned char *data = (const unsigned char *)vdata; - int len; - - if (datalen < 4) - return FALSE; - - len = toint(GET_32BIT(data)); - if (len < 0 || len > datalen - 4) - return FALSE; - - if (outlen) - *outlen = len + 4; /* total size including length field */ - if (out) - *out = dupprintf("%.*s", len, (char *)data + 4); - return TRUE; -} - -static char *getstring(const void *data, int datalen) -{ - char *ret; - if (getstring_inner(data, datalen, &ret, NULL)) - return ret; - else - return NULL; -} - -static int getstring_size(const void *data, int datalen) -{ - int ret; - if (getstring_inner(data, datalen, NULL, &ret)) - return ret; - else - return -1; } /* - * Append a message to the end of an xchannel's queue, with the length - * and type code filled in and the data block allocated but - * uninitialised. + * Append a message to the end of an xchannel's queue. */ -struct share_xchannel_message *share_xchannel_add_message -(struct share_xchannel *xc, int type, int len) +static void share_xchannel_add_message( + struct share_xchannel *xc, int type, const void *data, int len) { - unsigned char *block; struct share_xchannel_message *msg; /* - * Be a little tricksy here by allocating a single memory block - * containing both the 'struct share_xchannel_message' and the - * actual data. Simplifies freeing it later. + * Allocate the 'struct share_xchannel_message' and the actual + * data in one unit. */ - block = smalloc(sizeof(struct share_xchannel_message) + len); - msg = (struct share_xchannel_message *)block; - msg->data = block + sizeof(struct share_xchannel_message); + 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. @@ -1007,8 +989,6 @@ struct share_xchannel_message *share_xchannel_add_message xc->msghead = msg; msg->next = NULL; xc->msgtail = msg; - - return msg; } void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, @@ -1018,7 +998,7 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, * Handle queued incoming messages from the server destined for an * xchannel which is dead (i.e. downstream sent OPEN_FAILURE). */ - int delete = FALSE; + bool delete = false; while (xc->msghead) { struct share_xchannel_message *msg = xc->msghead; xc->msghead = msg->next; @@ -1028,27 +1008,31 @@ void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs, * A CHANNEL_REQUEST is responded to by sending * CHANNEL_FAILURE, if it has want_reply set. */ - int wantreplypos = getstring_size(msg->data, msg->datalen); - if (wantreplypos > 0 && wantreplypos < msg->datalen && - msg->data[wantreplypos] != 0) { - unsigned char id[4]; - PUT_32BIT(id, xc->server_id); + 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->ssh, cs->id, SSH2_MSG_CHANNEL_FAILURE, id, 4, + (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; + delete = true; } sfree(msg); } xc->msgtail = NULL; if (delete) { - ssh_delete_sharing_channel(cs->parent->ssh, xc->upstream_id); + ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id); share_remove_xchannel(cs, xc); } } @@ -1058,7 +1042,7 @@ void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, struct share_channel *chan, unsigned downstream_window) { - unsigned char window_adjust[8]; + strbuf *packet; /* * Send all the queued messages downstream. @@ -1080,12 +1064,14 @@ void share_xchannel_confirmation(struct ssh_sharing_connstate *cs, * size downstream thinks it's presented with the one we've * actually presented. */ - PUT_32BIT(window_adjust, xc->server_id); - PUT_32BIT(window_adjust + 4, downstream_window - xc->window); - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, - SSH2_MSG_CHANNEL_WINDOW_ADJUST, - window_adjust, 8, "window adjustment after" - " downstream accepted X channel"); + 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, @@ -1095,21 +1081,23 @@ void share_xchannel_failure(struct ssh_sharing_connstate *cs, * If downstream refuses to open our X channel at all for some * reason, we must respond by sending an emergency CLOSE upstream. */ - unsigned char id[4]; - PUT_32BIT(id, xc->server_id); - ssh_send_packet_from_downstream - (cs->parent->ssh, cs->id, SSH2_MSG_CHANNEL_CLOSE, id, 4, - "downstream refused X channel open"); + 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; + xc->live = false; share_dead_xchannel_respond(cs, xc); } -void share_setup_x11_channel(void *csv, void *chanv, +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, @@ -1117,14 +1105,10 @@ void share_setup_x11_channel(void *csv, void *chanv, int protomajor, int protominor, const void *initial_data, int initial_len) { - struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv; - struct share_channel *chan = (struct share_channel *)chanv; struct share_xchannel *xc; - struct share_xchannel_message *msg; void *greeting; int greeting_len; - unsigned char *pkt; - int pktlen; + strbuf *packet; /* * Create an xchannel containing data we've already received from @@ -1137,66 +1121,70 @@ void share_setup_x11_channel(void *csv, void *chanv, chan->x11_auth_proto, chan->x11_auth_data, chan->x11_auth_datalen, peer_addr, peer_port, &greeting_len); - msg = share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, - 8 + greeting_len + initial_len); - /* leave the channel id field unfilled - we don't know the - * downstream id yet, of course */ - PUT_32BIT(msg->data + 4, greeting_len + initial_len); - memcpy(msg->data + 8, greeting, greeting_len); - memcpy(msg->data + 8 + greeting_len, initial_data, initial_len); + packet = strbuf_new(); + 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. */ - pktlen = 27 + strlen(peer_addr); - pkt = snewn(pktlen, unsigned char); - PUT_32BIT(pkt, 3); /* strlen("x11") */ - memcpy(pkt+4, "x11", 3); - PUT_32BIT(pkt+7, server_id); - PUT_32BIT(pkt+11, server_currwin); - PUT_32BIT(pkt+15, server_maxpkt); - PUT_32BIT(pkt+19, strlen(peer_addr)); - memcpy(pkt+23, peer_addr, strlen(peer_addr)); - PUT_32BIT(pkt+23+strlen(peer_addr), peer_port); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, pkt, pktlen, NULL); - sfree(pkt); + 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_sharing_remove_x11_display(cs->parent->ssh, + 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 = 0; + chan->x11_one_shot = false; } } -void share_got_pkt_from_server(void *csv, int type, - unsigned char *pkt, int pktlen) +void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, + const void *vpkt, int pktlen) { - struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv; + const unsigned char *pkt = (const unsigned char *)vpkt; struct share_globreq *globreq; - int id_pos; + 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; + globreq->fwd->active = true; } } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) { if (type == SSH2_MSG_REQUEST_SUCCESS) { @@ -1220,9 +1208,9 @@ void share_got_pkt_from_server(void *csv, int type, break; case SSH2_MSG_CHANNEL_OPEN: - id_pos = getstring_size(pkt, pktlen); - assert(id_pos >= 0); - server_id = GET_32BIT(pkt + id_pos); + 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); @@ -1243,14 +1231,17 @@ void share_got_pkt_from_server(void *csv, int type, * first uint32 field in the packet. Substitute the downstream * channel id for our one and pass the packet downstream. */ - assert(pktlen >= 4); - upstream_id = GET_32BIT(pkt); + 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. */ - PUT_32BIT(pkt, chan->downstream_id); - send_packet_to_downstream(cs, type, pkt, pktlen, chan); + unsigned char *rewritten = snewn(pktlen, unsigned char); + memcpy(rewritten, pkt, pktlen); + PUT_32BIT(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. @@ -1267,11 +1258,11 @@ void share_got_pkt_from_server(void *csv, int type, } } } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) { - ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id); + 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->ssh, + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); share_remove_channel(cs, chan); if (!cs->sock) { @@ -1290,10 +1281,7 @@ void share_got_pkt_from_server(void *csv, int type, * The unusual case: this id refers to an xchannel. Add it * to the xchannel's queue. */ - struct share_xchannel_message *msg; - - msg = share_xchannel_add_message(xc, type, pktlen); - memcpy(msg->data, pkt, pktlen); + share_xchannel_add_message(xc, type, pkt, pktlen); /* If the xchannel is dead, then also respond to it (which * may involve deleting the channel). */ @@ -1312,15 +1300,22 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, int type, unsigned char *pkt, int pktlen) { - char *request_name; + ptrlen request_name; struct share_forwarding *fwd; - int id_pos; + 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: @@ -1341,39 +1336,27 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * will probably require that too, and so we don't forward on * any request we don't understand. */ - request_name = getstring(pkt, pktlen); - if (request_name == NULL) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } + request_name = get_string(src); + wantreplypos = src->pos; + orig_wantreply = get_bool(src); - if (!strcmp(request_name, "tcpip-forward")) { - int wantreplypos, orig_wantreply, port, ret; + if (ptrlen_eq_string(request_name, "tcpip-forward")) { + ptrlen hostpl; char *host; - - sfree(request_name); + 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. */ - wantreplypos = getstring_size(pkt, pktlen); - if (wantreplypos < 0 || wantreplypos >= pktlen) { + hostpl = get_string(src); + port = toint(get_uint32(src)); + if (get_err(src)) { err = dupprintf("Truncated GLOBAL_REQUEST packet"); goto confused; } - orig_wantreply = pkt[wantreplypos]; - port = getstring_size(pkt + (wantreplypos + 1), - pktlen - (wantreplypos + 1)); - port += (wantreplypos + 1); - if (port < 0 || port > pktlen - 4) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = getstring(pkt + (wantreplypos + 1), - pktlen - (wantreplypos + 1)); - assert(host != NULL); - port = GET_32BIT(pkt + port); + host = mkstr(hostpl); /* * See if we can allocate space in ssh.c's tree of remote @@ -1383,8 +1366,9 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * ourselves to manufacture a failure packet and send it * back to downstream. */ - ret = ssh_alloc_sharing_rportfwd(cs->parent->ssh, host, port, cs); - if (!ret) { + 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); @@ -1397,13 +1381,12 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * that we know whether this forwarding needs to be * cleaned up if downstream goes away. */ - int old_wantreply = pkt[wantreplypos]; pkt[wantreplypos] = 1; ssh_send_packet_from_downstream - (cs->parent->ssh, cs->id, type, pkt, pktlen, - old_wantreply ? NULL : "upstream added want_reply flag"); + (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->ssh, cs); + ssh_sharing_queue_global_request(cs->parent->cl, cs); if (fwd) { globreq = snew(struct share_globreq); @@ -1415,38 +1398,29 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, globreq->fwd = fwd; globreq->want_reply = orig_wantreply; globreq->type = GLOBREQ_TCPIP_FORWARD; + + fwd->rpf = rpf; } } sfree(host); - } else if (!strcmp(request_name, "cancel-tcpip-forward")) { - int wantreplypos, orig_wantreply, port; + } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { + ptrlen hostpl; char *host; + int port; struct share_forwarding *fwd; - sfree(request_name); - /* * Pick the packet apart to find the want_reply field and * the host/port we're going to ask to listen on. */ - wantreplypos = getstring_size(pkt, pktlen); - if (wantreplypos < 0 || wantreplypos >= pktlen) { + hostpl = get_string(src); + port = toint(get_uint32(src)); + if (get_err(src)) { err = dupprintf("Truncated GLOBAL_REQUEST packet"); goto confused; } - orig_wantreply = pkt[wantreplypos]; - port = getstring_size(pkt + (wantreplypos + 1), - pktlen - (wantreplypos + 1)); - port += (wantreplypos + 1); - if (port < 0 || port > pktlen - 4) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = getstring(pkt + (wantreplypos + 1), - pktlen - (wantreplypos + 1)); - assert(host != NULL); - port = GET_32BIT(pkt + port); + host = mkstr(hostpl); /* * Look up the existing forwarding with these details. @@ -1458,18 +1432,37 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, "", 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. */ - int old_wantreply = pkt[wantreplypos]; pkt[wantreplypos] = 1; ssh_send_packet_from_downstream - (cs->parent->ssh, cs->id, type, pkt, pktlen, - old_wantreply ? NULL : "upstream added want_reply flag"); - ssh_sharing_queue_global_request(cs->parent->ssh, cs); + (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); @@ -1478,16 +1471,7 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * Request we don't understand. Manufacture a failure * message if an answer was required. */ - int wantreplypos; - - sfree(request_name); - - wantreplypos = getstring_size(pkt, pktlen); - if (wantreplypos < 0 || wantreplypos >= pktlen) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - if (pkt[wantreplypos]) + if (orig_wantreply) send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, "", 0, NULL); } @@ -1495,18 +1479,19 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, case SSH2_MSG_CHANNEL_OPEN: /* Sender channel id comes after the channel type string */ - id_pos = getstring_size(pkt, pktlen); - if (id_pos < 0 || id_pos > pktlen - 12) { + 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; } - - old_id = GET_32BIT(pkt + id_pos); - new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs); - share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, - GET_32BIT(pkt + id_pos + 8)); + share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); PUT_32BIT(pkt + id_pos, new_id); - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, type, pkt, pktlen, NULL); break; @@ -1516,14 +1501,20 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, goto confused; } - id_pos = 4; /* sender channel id is 2nd uint32 field in packet */ - old_id = GET_32BIT(pkt + id_pos); + 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; + } - server_id = GET_32BIT(pkt); /* This server id may refer to either a halfchannel or an xchannel. */ hc = NULL, xc = NULL; /* placate optimiser */ if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { - new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs); + 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; @@ -1534,11 +1525,10 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, PUT_32BIT(pkt + id_pos, new_id); - chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, - GET_32BIT(pkt + 12)); + chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); if (hc) { - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, type, pkt, pktlen, NULL); share_remove_halfchannel(cs, hc); } else if (xc) { @@ -1554,15 +1544,15 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, break; case SSH2_MSG_CHANNEL_OPEN_FAILURE: - if (pktlen < 4) { + server_id = get_uint32(src); + if (get_err(src)) { err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); goto confused; } - server_id = GET_32BIT(pkt); /* This server id may refer to either a halfchannel or an xchannel. */ if ((hc = share_find_halfchannel(cs, server_id)) != NULL) { - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + 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)) @@ -1585,8 +1575,11 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, case SSH2_MSG_CHANNEL_FAILURE: case SSH2_MSG_IGNORE: case SSH2_MSG_DEBUG: - if (type == SSH2_MSG_CHANNEL_REQUEST && - (request_name = getstring(pkt + 4, pktlen - 4)) != NULL) { + 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 @@ -1612,18 +1605,17 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * subsequent CHANNEL_OPENs still can't be associated with * a parent session channel.) */ - if (!strcmp(request_name, "auth-agent-req@openssh.com") && - !ssh_agent_forwarding_permitted(cs->parent->ssh)) { - unsigned server_id = GET_32BIT(pkt); - unsigned char recipient_id[4]; - - sfree(request_name); + 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) { - PUT_32BIT(recipient_id, chan->downstream_id); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE, - recipient_id, 4, NULL); + 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); @@ -1643,14 +1635,11 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * whether it's one to handle locally or one to pass on to * a downstream, and if the latter, which one. */ - if (!strcmp(request_name, "x11-req")) { - unsigned server_id = GET_32BIT(pkt); - int want_reply, single_connection, screen; - char *auth_proto_str, *auth_data; - int auth_proto, protolen, datalen; - int pos; - - sfree(request_name); + 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) { @@ -1665,44 +1654,34 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * Pick apart the whole message to find the downstream * auth details. */ - /* we have already seen: 4 bytes channel id, 4+7 request name */ - if (pktlen < 17) { - err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet"); - goto confused; - } - want_reply = pkt[15] != 0; - single_connection = pkt[16] != 0; - auth_proto_str = getstring(pkt+17, pktlen-17); - auth_proto = x11_identify_auth_proto(auth_proto_str); - sfree(auth_proto_str); - pos = 17 + getstring_size(pkt+17, pktlen-17); - auth_data = getstring(pkt+pos, pktlen-pos); - pos += getstring_size(pkt+pos, pktlen-pos); - - if (pktlen < pos+4) { - err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet"); - sfree(auth_data); + 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; } - screen = GET_32BIT(pkt+pos); if (auth_proto < 0) { /* Reject due to not understanding downstream's * requested authorisation method. */ - unsigned char recipient_id[4]; - PUT_32BIT(recipient_id, chan->downstream_id); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_FAILURE, - recipient_id, 4, NULL); - sfree(auth_data); + 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); - sfree(auth_data); chan->x11_auth_upstream = - ssh_sharing_add_x11_display(cs->parent->ssh, auth_proto, + ssh_add_sharing_x11_display(cs->parent->cl, auth_proto, cs, chan); chan->x11_one_shot = single_connection; @@ -1711,40 +1690,30 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * containing our own auth data, and send that to the * server. */ - protolen = strlen(chan->x11_auth_upstream->protoname); - datalen = strlen(chan->x11_auth_upstream->datastring); - pktlen = 29+protolen+datalen; - pkt = snewn(pktlen, unsigned char); - PUT_32BIT(pkt, server_id); - PUT_32BIT(pkt+4, 7); /* strlen("x11-req") */ - memcpy(pkt+8, "x11-req", 7); - pkt[15] = want_reply; - pkt[16] = single_connection; - PUT_32BIT(pkt+17, protolen); - memcpy(pkt+21, chan->x11_auth_upstream->protoname, protolen); - PUT_32BIT(pkt+21+protolen, datalen); - memcpy(pkt+25+protolen, chan->x11_auth_upstream->datastring, - datalen); - PUT_32BIT(pkt+25+protolen+datalen, screen); - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, - SSH2_MSG_CHANNEL_REQUEST, - pkt, pktlen, NULL); - sfree(pkt); + packet = strbuf_new(); + 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; } - - sfree(request_name); } - ssh_send_packet_from_downstream(cs->parent->ssh, cs->id, + ssh_send_packet_from_downstream(cs->parent->cl, cs->id, type, pkt, pktlen, NULL); if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { - server_id = GET_32BIT(pkt); chan = share_find_channel_by_server(cs, server_id); if (chan) { if (chan->state == RCVD_CLOSE) { - ssh_delete_sharing_channel(cs->parent->ssh, + ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id); share_remove_channel(cs, chan); } else { @@ -1775,19 +1744,20 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, * Coroutine macros similar to, but simplified from, those in ssh.c. */ #define crBegin(v) { int *crLine = &v; switch(v) { case 0:; -#define crFinish(z) } *crLine = 0; return (z); } +#define crFinishV } *crLine = 0; return; } #define crGetChar(c) do \ { \ while (len == 0) { \ - *crLine =__LINE__; return 1; case __LINE__:; \ + *crLine =__LINE__; return; case __LINE__:; \ } \ len--; \ (c) = (unsigned char)*data++; \ } while (0) -static int share_receive(Plug plug, int urgent, char *data, int len) +static void share_receive(Plug *plug, int urgent, char *data, int len) { - struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; + 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; @@ -1825,10 +1795,9 @@ static int share_receive(Plug plug, int urgent, char *data, int len) } if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') cs->recvlen--; /* trim off \r before \n */ - ssh_sharing_logf(cs->parent->ssh, cs->id, - "Downstream version string: %.*s", - cs->recvlen, cs->recvbuf); - cs->got_verstring = TRUE; + log_downstream(cs, "Downstream version string: %.*s", + cs->recvlen, cs->recvbuf); + cs->got_verstring = true; /* * Loop round reading packets. @@ -1858,12 +1827,13 @@ static int share_receive(Plug plug, int urgent, char *data, int len) } dead:; - crFinish(1); + crFinishV; } -static void share_sent(Plug plug, int bufsize) +static void share_sent(Plug *plug, int bufsize) { - /* struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; */ + /* ssh_sharing_connstate *cs = container_of( + plug, ssh_sharing_connstate, plug); */ /* * We do nothing here, because we expect that there won't be a @@ -1875,41 +1845,39 @@ static void share_sent(Plug plug, int bufsize) */ } -static int share_listen_closing(Plug plug, const char *error_msg, - int error_code, int calling_back) +static void share_listen_closing(Plug *plug, const char *error_msg, + int error_code, bool calling_back) { - struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; + ssh_sharing_state *sharestate = + container_of(plug, ssh_sharing_state, plug); if (error_msg) - ssh_sharing_logf(sharestate->ssh, 0, - "listening socket: %s", error_msg); + log_general(sharestate, "listening socket: %s", error_msg); sk_close(sharestate->listensock); sharestate->listensock = NULL; - return 1; } -static void share_send_verstring(struct ssh_sharing_connstate *cs) +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", NULL); sk_write(cs->sock, fullstring, strlen(fullstring)); sfree(fullstring); - cs->sent_verstring = TRUE; + cs->sent_verstring = true; } -int share_ndownstreams(void *state) +int share_ndownstreams(ssh_sharing_state *sharestate) { - struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state; return count234(sharestate->connections); } -void share_activate(void *state, const char *server_verstring) +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_state *sharestate = (struct ssh_sharing_state *)state; struct ssh_sharing_connstate *cs; int i; @@ -1933,26 +1901,28 @@ void share_activate(void *state, const char *server_verstring) } } -static int share_listen_accepting(Plug plug, +static const PlugVtable ssh_sharing_conn_plugvt = { + NULL, /* no log function, because that's for outgoing connections */ + share_closing, + share_receive, + share_sent, + NULL /* no accepting function, because we've already done it */ +}; + +static int share_listen_accepting(Plug *plug, accept_fn_t constructor, accept_ctx_t ctx) { - static const struct plug_function_table connection_fn_table = { - NULL, /* no log function, because that's for outgoing connections */ - share_closing, - share_receive, - share_sent, - NULL /* no accepting function, because we've already done it */ - }; - struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug; + struct ssh_sharing_state *sharestate = container_of( + plug, struct ssh_sharing_state, plug); struct ssh_sharing_connstate *cs; const char *err; - char *peerinfo; + SocketPeerInfo *peerinfo; /* * A new downstream has connected to us. */ cs = snew(struct ssh_sharing_connstate); - cs->fn = &connection_fn_table; + cs->plug.vt = &ssh_sharing_conn_plugvt; cs->parent = sharestate; if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 && @@ -1964,7 +1934,7 @@ static int share_listen_accepting(Plug plug, if (sharestate->nextid == 0) sharestate->nextid++; /* only happens in VERY long-running upstreams */ - cs->sock = constructor(ctx, (Plug) cs); + cs->sock = constructor(ctx, &cs->plug); if ((err = sk_socket_error(cs->sock)) != NULL) { sfree(cs); return err != NULL; @@ -1974,11 +1944,11 @@ static int share_listen_accepting(Plug plug, add234(cs->parent->connections, cs); - cs->sent_verstring = FALSE; + cs->sent_verstring = false; if (sharestate->server_verstring) share_send_verstring(cs); - cs->got_verstring = FALSE; + cs->got_verstring = false; cs->recvlen = 0; cs->crLine = 0; cs->halfchannels = newtree234(share_halfchannel_cmp); @@ -1990,17 +1960,14 @@ static int share_listen_accepting(Plug plug, cs->globreq_head = cs->globreq_tail = NULL; peerinfo = sk_peer_info(cs->sock); - ssh_sharing_downstream_connected(sharestate->ssh, cs->id, peerinfo); - sfree(peerinfo); + 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; } -/* Per-application overrides for what roles we can take (e.g. pscp - * will never be an upstream) */ -extern const int share_can_be_downstream; -extern const int share_can_be_upstream; - /* * Decide on the string used to identify the connection point between * upstream and downstream (be it a Windows named pipe or a @@ -2045,39 +2012,18 @@ char *ssh_share_sockname(const char *host, int port, Conf *conf) return sockname; } -static void nullplug_socket_log(Plug plug, int type, SockAddr addr, int port, - const char *error_msg, int error_code) {} -static int nullplug_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) { return 0; } -static int nullplug_receive(Plug plug, int urgent, char *data, - int len) { return 0; } -static void nullplug_sent(Plug plug, int bufsize) {} - -int ssh_share_test_for_upstream(const char *host, int port, Conf *conf) +bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) { - static const struct plug_function_table fn_table = { - nullplug_socket_log, - nullplug_closing, - nullplug_receive, - nullplug_sent, - NULL - }; - struct nullplug { - const struct plug_function_table *fn; - } np; - char *sockname, *logtext, *ds_err, *us_err; int result; - Socket sock; - - np.fn = &fn_table; + Socket *sock; sockname = ssh_share_sockname(host, port, conf); sock = NULL; logtext = ds_err = us_err = NULL; - result = platform_ssh_share(sockname, conf, (Plug)&np, (Plug)NULL, &sock, - &logtext, &ds_err, &us_err, FALSE, TRUE); + result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, + &logtext, &ds_err, &us_err, false, true); sfree(logtext); sfree(ds_err); @@ -2086,14 +2032,28 @@ int ssh_share_test_for_upstream(const char *host, int port, Conf *conf) if (result == SHARE_NONE) { assert(sock == NULL); - return FALSE; + return false; } else { assert(result == SHARE_DOWNSTREAM); sk_close(sock); - return TRUE; + return true; } } +static const PlugVtable ssh_sharing_listen_plugvt = { + NULL, /* no log function, because that's for outgoing connections */ + share_listen_closing, + NULL, /* no receive function on a listening socket */ + NULL, /* no sent function on a listening socket */ + share_listen_accepting +}; + +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 @@ -2104,29 +2064,23 @@ int ssh_share_test_for_upstream(const char *host, int port, Conf *conf) * to the upstream; otherwise (whether or not we have established an * upstream) we return NULL. */ -Socket ssh_connection_sharing_init(const char *host, int port, - Conf *conf, Ssh ssh, void **state) +Socket *ssh_connection_sharing_init( + const char *host, int port, Conf *conf, LogContext *logctx, + Plug *sshplug, ssh_sharing_state **state) { - static const struct plug_function_table listen_fn_table = { - NULL, /* no log function, because that's for outgoing connections */ - share_listen_closing, - NULL, /* no receive function on a listening socket */ - NULL, /* no sent function on a listening socket */ - share_listen_accepting - }; - - int result, can_upstream, can_downstream; + int result; + bool can_upstream, can_downstream; char *logtext, *ds_err, *us_err; char *sockname; - Socket sock; + Socket *sock, *toret = NULL; struct ssh_sharing_state *sharestate; - if (!conf_get_int(conf, CONF_ssh_connection_sharing)) + if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) return NULL; /* do not share anything */ can_upstream = share_can_be_upstream && - conf_get_int(conf, CONF_ssh_connection_sharing_upstream); + conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); can_downstream = share_can_be_downstream && - conf_get_int(conf, CONF_ssh_connection_sharing_downstream); + conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); if (!can_upstream && !can_downstream) return NULL; @@ -2137,8 +2091,9 @@ Socket ssh_connection_sharing_init(const char *host, int port, * to be an upstream. */ sharestate = snew(struct ssh_sharing_state); - sharestate->fn = &listen_fn_table; + 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 @@ -2149,13 +2104,9 @@ Socket ssh_connection_sharing_init(const char *host, int port, */ sock = NULL; logtext = ds_err = us_err = NULL; - result = platform_ssh_share(sockname, conf, (Plug)ssh, - (Plug)sharestate, &sock, &logtext, &ds_err, - &us_err, can_upstream, can_downstream); - ssh_connshare_log(ssh, result, logtext, ds_err, us_err); - sfree(logtext); - sfree(ds_err); - sfree(us_err); + result = platform_ssh_share( + sockname, conf, sshplug, &sharestate->plug, &sock, &logtext, + &ds_err, &us_err, can_upstream, can_downstream); switch (result) { case SHARE_NONE: /* @@ -2163,11 +2114,29 @@ Socket ssh_connection_sharing_init(const char *host, int port, * 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); - return NULL; + break; case SHARE_DOWNSTREAM: /* @@ -2175,10 +2144,15 @@ Socket ssh_connection_sharing_init(const char *host, int port, * 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); - return sock; + toret = sock; + break; case SHARE_UPSTREAM: /* @@ -2186,15 +2160,21 @@ Socket ssh_connection_sharing_init(const char *host, int port, * 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->ssh = ssh; sharestate->server_verstring = NULL; sharestate->sockname = sockname; sharestate->nextid = 1; - return NULL; + break; } - return NULL; + sfree(logtext); + sfree(ds_err); + sfree(us_err); + return toret; } diff --git a/sshsignals.h b/sshsignals.h new file mode 100644 index 00000000..b213c34f --- /dev/null +++ b/sshsignals.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/sshttymodes.h b/sshttymodes.h new file mode 100644 index 00000000..8fffe1c5 --- /dev/null +++ b/sshttymodes.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/sshverstring.c b/sshverstring.c new file mode 100644 index 00000000..529b5964 --- /dev/null +++ b/sshverstring.c @@ -0,0 +1,635 @@ +/* + * 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; + char *vstring; + int vslen, vstrsize; + 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 struct BinaryPacketProtocolVtable ssh_verstring_vtable = { + ssh_verstring_free, + ssh_verstring_handle_input, + ssh_verstring_handle_output, + ssh_verstring_new_pktout, + ssh_verstring_queue_disconnect, +}; + +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); + + /* + * 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); + sfree(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 \ + { \ + crMaybeWaitUntilV( \ + s->bpp.input_eof || \ + bufchain_size(s->bpp.in_raw) >= (minlen)); \ + if (s->bpp.input_eof) \ + 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); + break; + } + + /* + * If we didn't find it, consume data until we see a newline. + */ + while (1) { + int len; + void *data; + char *nl; + + /* Wait to receive at least 1 byte, but then consume more + * than that if it's there. */ + BPP_WAITFOR(1); + bufchain_prefix(s->bpp.in_raw, &data, &len); + if ((nl = memchr(data, '\012', len)) != NULL) { + bufchain_consume(s->bpp.in_raw, nl - (char *)data + 1); + break; + } else { + bufchain_consume(s->bpp.in_raw, len); + } + } + } + + s->found_prefix = true; + + /* + * Start a buffer to store the full greeting line. + */ + s->vstrsize = s->prefix_wanted.len + 16; + s->vstring = snewn(s->vstrsize, char); + memcpy(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len); + s->vslen = s->prefix_wanted.len; + + /* + * Now read the rest of the greeting line. + */ + s->i = 0; + do { + int len; + void *data; + char *nl; + + crMaybeWaitUntilV(bufchain_size(s->bpp.in_raw) > 0); + bufchain_prefix(s->bpp.in_raw, &data, &len); + if ((nl = memchr(data, '\012', len)) != NULL) { + len = nl - (char *)data + 1; + } + + if (s->vslen + len >= s->vstrsize - 1) { + s->vstrsize = (s->vslen + len) * 5 / 4 + 32; + s->vstring = sresize(s->vstring, s->vstrsize, char); + } + + memcpy(s->vstring + s->vslen, data, len); + s->vslen += len; + bufchain_consume(s->bpp.in_raw, len); + + } while (s->vstring[s->vslen-1] != '\012'); + + /* + * Trim \r and \n from the version string, and replace them with + * a NUL terminator. + */ + while (s->vslen > 0 && + (s->vstring[s->vslen-1] == '\r' || + s->vstring[s->vslen-1] == '\n')) + s->vslen--; + s->vstring[s->vslen] = '\0'; + + bpp_logevent(("Remote version: %s", s->vstring)); + + /* + * 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->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) +{ + assert(0 && "Should never try to send packets during SSH version " + "string exchange"); + return NULL; +} + +static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp) +{ + if (pq_peek(&bpp->out_pq)) { + assert(0 && "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; +} + +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 index 60447fdf..8171ee11 100644 --- a/sshzlib.c +++ b/sshzlib.c @@ -41,6 +41,8 @@ #include #include +#include "defs.h" + #ifdef ZLIB_STANDALONE /* @@ -57,15 +59,14 @@ #define sresize(x, n, type) ( (type *) realloc((x), (n) * sizeof(type)) ) #define sfree(x) ( free((x)) ) +typedef struct { const struct dummy *vt; } ssh_compressor; +typedef struct { const struct dummy *vt; } ssh_decompressor; +static const struct dummy { int i; } ssh_zlib; + #else #include "ssh.h" #endif -#ifndef FALSE -#define FALSE 0 -#define TRUE (!FALSE) -#endif - /* ---------------------------------------------------------------------- * Basic LZ77 code. This bit is designed modularly, so it could be * ripped out and used in a different LZ77 compressor. Go to it, @@ -89,11 +90,11 @@ 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 + * If `compress' is false, it will never emit a match, but will * instead call literal() for everything. */ static void lz77_compress(struct LZ77Context *ctx, - unsigned char *data, int len, int compress); + unsigned char *data, int len, bool compress); /* * Modifiable parameters. @@ -198,7 +199,7 @@ static void lz77_advance(struct LZ77InternalContext *st, #define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] ) static void lz77_compress(struct LZ77Context *ctx, - unsigned char *data, int len, int compress) + unsigned char *data, int len, bool compress) { struct LZ77InternalContext *st = ctx->ictx; int i, distance, off, nmatch, matchlen, advance; @@ -369,8 +370,7 @@ struct Outbuf { int outlen, outsize; unsigned long outbits; int noutbits; - int firstblock; - int comp_disabled; + bool firstblock; }; static void outbits(struct Outbuf *out, unsigned long bits, int nbits) @@ -498,14 +498,6 @@ static void zlib_literal(struct LZ77Context *ectx, unsigned char c) { struct Outbuf *out = (struct Outbuf *) ectx->userdata; - if (out->comp_disabled) { - /* - * We're in an uncompressed block, so just output the byte. - */ - outbits(out, c, 8); - return; - } - if (c <= 143) { /* 0 through 143 are 8 bits long starting at 00110000. */ outbits(out, mirrorbytes[0x30 + c], 8); @@ -521,8 +513,6 @@ static void zlib_match(struct LZ77Context *ectx, int distance, int len) int i, j, k; struct Outbuf *out = (struct Outbuf *) ectx->userdata; - assert(!out->comp_disabled); - while (len > 0) { int thislen; @@ -607,80 +597,46 @@ static void zlib_match(struct LZ77Context *ectx, int distance, int len) } } -void *zlib_compress_init(void) +struct ssh_zlib_compressor { + struct LZ77Context ectx; + ssh_compressor sc; +}; + +ssh_compressor *zlib_compress_init(void) { struct Outbuf *out; - struct LZ77Context *ectx = snew(struct LZ77Context); + struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor); - lz77_init(ectx); - ectx->literal = zlib_literal; - ectx->match = zlib_match; + lz77_init(&comp->ectx); + comp->sc.vt = &ssh_zlib; + comp->ectx.literal = zlib_literal; + comp->ectx.match = zlib_match; out = snew(struct Outbuf); out->outbits = out->noutbits = 0; - out->firstblock = 1; - out->comp_disabled = FALSE; - ectx->userdata = out; + out->firstblock = true; + comp->ectx.userdata = out; - return ectx; + return &comp->sc; } -void zlib_compress_cleanup(void *handle) +void zlib_compress_cleanup(ssh_compressor *sc) { - struct LZ77Context *ectx = (struct LZ77Context *)handle; - sfree(ectx->userdata); - sfree(ectx->ictx); - sfree(ectx); -} - -/* - * Turn off actual LZ77 analysis for one block, to facilitate - * construction of a precise-length IGNORE packet. Returns the - * length adjustment (which is only valid for packets < 65536 - * bytes, but that seems reasonable enough). - */ -static int zlib_disable_compression(void *handle) -{ - struct LZ77Context *ectx = (struct LZ77Context *)handle; - struct Outbuf *out = (struct Outbuf *) ectx->userdata; - int n; - - out->comp_disabled = TRUE; - - n = 0; - /* - * If this is the first block, we will start by outputting two - * header bytes, and then three bits to begin an uncompressed - * block. This will cost three bytes (because we will start on - * a byte boundary, this is certain). - */ - if (out->firstblock) { - n = 3; - } else { - /* - * Otherwise, we will output seven bits to close the - * previous static block, and _then_ three bits to begin an - * uncompressed block, and then flush the current byte. - * This may cost two bytes or three, depending on noutbits. - */ - n += (out->noutbits + 10) / 8; - } - - /* - * Now we output four bytes for the length / ~length pair in - * the uncompressed block. - */ - n += 4; - - return n; + struct ssh_zlib_compressor *comp = + container_of(sc, struct ssh_zlib_compressor, sc); + sfree(comp->ectx.userdata); + sfree(comp->ectx.ictx); + sfree(comp); } -int zlib_compress_block(void *handle, unsigned char *block, int len, - unsigned char **outblock, int *outlen) +void zlib_compress_block(ssh_compressor *sc, unsigned char *block, int len, + unsigned char **outblock, int *outlen, + int minlen) { - struct LZ77Context *ectx = (struct LZ77Context *)handle; - struct Outbuf *out = (struct Outbuf *) ectx->userdata; - int in_block; + struct ssh_zlib_compressor *comp = + container_of(sc, struct ssh_zlib_compressor, sc); + struct Outbuf *out = (struct Outbuf *) comp->ectx.userdata; + bool in_block; out->outbuf = NULL; out->outlen = out->outsize = 0; @@ -692,108 +648,66 @@ int zlib_compress_block(void *handle, unsigned char *block, int len, */ if (out->firstblock) { outbits(out, 0x9C78, 16); - out->firstblock = 0; + out->firstblock = false; - in_block = FALSE; + in_block = false; } else - in_block = TRUE; - - if (out->comp_disabled) { - if (in_block) - outbits(out, 0, 7); /* close static block */ - - while (len > 0) { - int blen = (len < 65535 ? len : 65535); - - /* - * Start a Deflate (RFC1951) uncompressed block. We - * transmit a zero bit (BFINAL=0), followed by two more - * zero bits (BTYPE=00). Of course these are in the - * wrong order (00 0), not that it matters. - */ - outbits(out, 0, 3); + in_block = true; - /* - * Output zero bits to align to a byte boundary. - */ - if (out->noutbits) - outbits(out, 0, 8 - out->noutbits); - - /* - * Output the block length, and then its one's - * complement. They're little-endian, so all we need to - * do is pass them straight to outbits() with bit count - * 16. - */ - outbits(out, blen, 16); - outbits(out, blen ^ 0xFFFF, 16); - - /* - * Do the `compression': we need to pass the data to - * lz77_compress so that it will be taken into account - * for subsequent (distance,length) pairs. But - * lz77_compress is passed FALSE, which means it won't - * actually find (or even look for) any matches; so - * every character will be passed straight to - * zlib_literal which will spot out->comp_disabled and - * emit in the uncompressed format. - */ - lz77_compress(ectx, block, blen, FALSE); + 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); + } - len -= blen; - block += blen; - } - outbits(out, 2, 3); /* open new block */ - } else { - if (!in_block) { - /* - * Start a Deflate (RFC1951) fixed-trees block. We - * transmit a zero bit (BFINAL=0), followed by a zero - * bit and a one bit (BTYPE=01). Of course these are in - * the wrong order (01 0). - */ - outbits(out, 2, 3); - } + /* + * Do the compression. + */ + lz77_compress(&comp->ectx, block, len, true); - /* - * Do the compression. - */ - lz77_compress(ectx, block, len, TRUE); + /* + * End the block (by transmitting code 256, which is + * 0000000 in fixed-tree mode), and transmit some empty + * blocks to ensure we have emitted the byte containing the + * last piece of genuine data. There are three ways we can + * do this: + * + * - Minimal flush. Output end-of-block and then open a + * new static block. This takes 9 bits, which is + * guaranteed to flush out the last genuine code in the + * closed block; but allegedly zlib can't handle it. + * + * - Zlib partial flush. Output EOB, open and close an + * empty static block, and _then_ open the new block. + * This is the best zlib can handle. + * + * - Zlib sync flush. Output EOB, then an empty + * _uncompressed_ block (000, then sync to byte + * boundary, then send bytes 00 00 FF FF). Then open the + * new block. + * + * For the moment, we will use Zlib partial flush. + */ + outbits(out, 0, 7); /* close block */ + outbits(out, 2, 3 + 7); /* empty static block */ + outbits(out, 2, 3); /* open new block */ - /* - * 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->outlen < minlen) { + outbits(out, 0, 7); /* close block */ + outbits(out, 2, 3); /* open new static block */ } - out->comp_disabled = FALSE; - *outblock = out->outbuf; *outlen = out->outlen; - - return 1; } /* ---------------------------------------------------------------------- @@ -966,9 +880,11 @@ struct zlib_decompress_ctx { int winpos; unsigned char *outblk; int outlen, outsize; + + ssh_decompressor dc; }; -void *zlib_decompress_init(void) +ssh_decompressor *zlib_decompress_init(void) { struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx); unsigned char lengths[288]; @@ -986,12 +902,14 @@ void *zlib_decompress_init(void) dctx->nbits = 0; dctx->winpos = 0; - return dctx; + dctx->dc.vt = &ssh_zlib; + return &dctx->dc; } -void zlib_decompress_cleanup(void *handle) +void zlib_decompress_cleanup(ssh_decompressor *dc) { - struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle; + struct zlib_decompress_ctx *dctx = + container_of(dc, struct zlib_decompress_ctx, dc); if (dctx->currlentable && dctx->currlentable != dctx->staticlentable) zlib_freetable(&dctx->currlentable); @@ -1049,10 +967,11 @@ static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c) #define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) -int zlib_decompress_block(void *handle, unsigned char *block, int len, - unsigned char **outblock, int *outlen) +bool zlib_decompress_block(ssh_decompressor *dc, unsigned char *block, int len, + unsigned char **outblock, int *outlen) { - struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle; + 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[] = { @@ -1290,13 +1209,13 @@ int zlib_decompress_block(void *handle, unsigned char *block, int len, finished: *outblock = dctx->outblk; *outlen = dctx->outlen; - return 1; + return true; decode_error: sfree(dctx->outblk); *outblock = dctx->outblk = NULL; *outlen = 0; - return 0; + return false; } #ifdef ZLIB_STANDALONE @@ -1308,8 +1227,8 @@ int main(int argc, char **argv) { unsigned char buf[16], *outbuf; int ret, outlen; - void *handle; - int noheader = FALSE, opts = TRUE; + ssh_decompressor *handle; + int noheader = false, opts = true; char *filename = NULL; FILE *fp; @@ -1318,9 +1237,9 @@ int main(int argc, char **argv) if (p[0] == '-' && opts) { if (!strcmp(p, "-d")) - noheader = TRUE; + noheader = true; else if (!strcmp(p, "--")) - opts = FALSE; /* next thing is filename */ + opts = false; /* next thing is filename */ else { fprintf(stderr, "unknown command line option '%s'\n", p); return 1; @@ -1380,7 +1299,7 @@ int main(int argc, char **argv) #else -const struct ssh_compress ssh_zlib = { +const struct ssh_compression_alg ssh_zlib = { "zlib", "zlib@openssh.com", /* delayed version */ zlib_compress_init, @@ -1389,7 +1308,6 @@ const struct ssh_compress ssh_zlib = { zlib_decompress_init, zlib_decompress_cleanup, zlib_decompress_block, - zlib_disable_compression, "zlib (RFC1950)" }; diff --git a/storage.h b/storage.h index 8e07ef0c..41e4b5c9 100644 --- a/storage.h +++ b/storage.h @@ -28,12 +28,14 @@ * * Any returned error message must be freed after use. */ -void *open_settings_w(const char *sessionname, char **errmsg); -void write_setting_s(void *handle, const char *key, const char *value); -void write_setting_i(void *handle, const char *key, int value); -void write_setting_filename(void *handle, const char *key, Filename *value); -void write_setting_fontspec(void *handle, const char *key, FontSpec *font); -void close_settings_w(void *handle); +settings_w *open_settings_w(const char *sessionname, char **errmsg); +void write_setting_s(settings_w *handle, const char *key, const char *value); +void write_setting_i(settings_w *handle, const char *key, int value); +void write_setting_filename(settings_w *handle, + const char *key, Filename *value); +void write_setting_fontspec(settings_w *handle, + const char *key, FontSpec *font); +void close_settings_w(settings_w *handle); /* * Read a saved session. The caller is expected to call @@ -51,12 +53,12 @@ void close_settings_w(void *handle); * should invent a sensible default. If an integer setting is not * present, read_setting_i() returns its provided default. */ -void *open_settings_r(const char *sessionname); -char *read_setting_s(void *handle, const char *key); -int read_setting_i(void *handle, const char *key, int defvalue); -Filename *read_setting_filename(void *handle, const char *key); -FontSpec *read_setting_fontspec(void *handle, const char *key); -void close_settings_r(void *handle); +settings_r *open_settings_r(const char *sessionname); +char *read_setting_s(settings_r *handle, const char *key); +int read_setting_i(settings_r *handle, const char *key, int defvalue); +Filename *read_setting_filename(settings_r *handle, const char *key); +FontSpec *read_setting_fontspec(settings_r *handle, const char *key); +void close_settings_r(settings_r *handle); /* * Delete a whole saved session. @@ -66,9 +68,9 @@ void del_settings(const char *sessionname); /* * Enumerate all saved sessions. */ -void *enum_settings_start(void); -char *enum_settings_next(void *handle, char *buffer, int buflen); -void enum_settings_finish(void *handle); +settings_e *enum_settings_start(void); +bool enum_settings_next(settings_e *handle, strbuf *out); +void enum_settings_finish(settings_e *handle); /* ---------------------------------------------------------------------- * Functions to access PuTTY's host key database. diff --git a/telnet.c b/telnet.c index c4b04132..1287bd2d 100644 --- a/telnet.c +++ b/telnet.c @@ -8,13 +8,6 @@ #include "putty.h" -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - #define IAC 255 /* interpret as command: */ #define DONT 254 /* you are not to use option */ #define DO 253 /* please, you use option */ @@ -124,8 +117,6 @@ static const char *telopt(int opt) #undef telnet_str } -static void telnet_size(void *handle, int width, int height); - struct Opt { int send; /* what we initially send */ int nsend; /* -ve send if requested to stop it */ @@ -177,27 +168,26 @@ static const struct Opt *const opts[] = { &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL }; -typedef struct telnet_tag { - const struct plug_function_table *fn; - /* the above field _must_ be first in the structure */ - - Socket s; - int closed_on_socket_error; +typedef struct Telnet Telnet; +struct Telnet { + Socket *s; + bool closed_on_socket_error; - void *frontend; - void *ldisc; + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; int term_width, term_height; int opt_states[NUM_OPTS]; - int echoing, editing; - int activated; + bool echoing, editing; + bool activated; int bufsize; - int in_synch; + bool in_synch; int sb_opt, sb_len; unsigned char *sb_buf; int sb_size; - int session_started; + bool session_started; enum { TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, @@ -206,48 +196,48 @@ typedef struct telnet_tag { Conf *conf; - Pinger pinger; -} *Telnet; + Pinger *pinger; + + Plug plug; + Backend backend; +}; #define TELNET_MAX_BACKLOG 4096 #define SB_DELTA 1024 -static void c_write(Telnet telnet, const char *buf, int len) +static void c_write(Telnet *telnet, const void *buf, int len) { int backlog; - backlog = from_backend(telnet->frontend, 0, buf, len); + 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) +static void log_option(Telnet *telnet, const char *sender, int cmd, int option) { - char *buf; /* * The strange-looking "" below is there to avoid a * trigraph - a double question mark followed by > maps to a * closing brace character! */ - buf = dupprintf("%s:\t%s %s", sender, - (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : - cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), - telopt(option)); - logevent(telnet->frontend, buf); - sfree(buf); + 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) +static void send_opt(Telnet *telnet, int cmd, int option) { unsigned char b[3]; b[0] = IAC; b[1] = cmd; b[2] = option; - telnet->bufsize = sk_write(telnet->s, (char *)b, 3); + telnet->bufsize = sk_write(telnet->s, b, 3); log_option(telnet, "client", cmd, option); } -static void deactivate_option(Telnet telnet, const struct Opt *o) +static void deactivate_option(Telnet *telnet, const struct Opt *o) { if (telnet->opt_states[o->index] == REQUESTED || telnet->opt_states[o->index] == ACTIVE) @@ -258,7 +248,8 @@ static void deactivate_option(Telnet telnet, const struct Opt *o) /* * Generate side effects of enabling or disabling an option. */ -static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) +static void option_side_effects( + Telnet *telnet, const struct Opt *o, bool enabled) { if (o->option == TELOPT_ECHO && o->send == DO) telnet->echoing = !enabled; @@ -281,14 +272,15 @@ static void option_side_effects(Telnet telnet, const struct Opt *o, int enabled) telnet->opt_states[o_they_sga.index] = REQUESTED; send_opt(telnet, o_they_sga.send, o_they_sga.option); } - telnet->activated = TRUE; + telnet->activated = true; } } -static void activate_option(Telnet telnet, const struct Opt *o) +static void activate_option(Telnet *telnet, const struct Opt *o) { if (o->send == WILL && o->option == TELOPT_NAWS) - telnet_size(telnet, telnet->term_width, telnet->term_height); + backend_size(&telnet->backend, + telnet->term_width, telnet->term_height); if (o->send == WILL && (o->option == TELOPT_NEW_ENVIRON || o->option == TELOPT_OLD_ENVIRON)) { @@ -299,20 +291,20 @@ static void activate_option(Telnet telnet, const struct Opt *o) deactivate_option(telnet, o->option == TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv); } - option_side_effects(telnet, o, 1); + option_side_effects(telnet, o, true); } -static void refused_option(Telnet telnet, const struct Opt *o) +static void refused_option(Telnet *telnet, const struct Opt *o) { if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON && telnet->opt_states[o_oenv.index] == INACTIVE) { send_opt(telnet, WILL, TELOPT_OLD_ENVIRON); telnet->opt_states[o_oenv.index] = REQUESTED; } - option_side_effects(telnet, o, 0); + option_side_effects(telnet, o, false); } -static void proc_rec_opt(Telnet telnet, int cmd, int option) +static void proc_rec_opt(Telnet *telnet, int cmd, int option) { const struct Opt *const *o; @@ -345,7 +337,7 @@ static void proc_rec_opt(Telnet telnet, int cmd, int option) case ACTIVE: telnet->opt_states[(*o)->index] = INACTIVE; send_opt(telnet, (*o)->nsend, option); - option_side_effects(telnet, *o, 0); + option_side_effects(telnet, *o, false); break; case INACTIVE: case REALLY_INACTIVE: @@ -364,7 +356,7 @@ static void proc_rec_opt(Telnet telnet, int cmd, int option) send_opt(telnet, (cmd == WILL ? DONT : WONT), option); } -static void process_subneg(Telnet telnet) +static void process_subneg(Telnet *telnet) { unsigned char *b, *p, *q; int var, value, n, bsize; @@ -373,7 +365,6 @@ static void process_subneg(Telnet telnet) switch (telnet->sb_opt) { case TELOPT_TSPEED: if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { - char *logbuf; char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); b = snewn(20 + strlen(termspeed), unsigned char); b[0] = IAC; @@ -384,18 +375,15 @@ static void process_subneg(Telnet telnet) n = 4 + strlen(termspeed); b[n] = IAC; b[n + 1] = SE; - telnet->bufsize = sk_write(telnet->s, (char *)b, n + 2); - logevent(telnet->frontend, "server:\tSB TSPEED SEND"); - logbuf = dupprintf("client:\tSB TSPEED IS %s", termspeed); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + 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->frontend, "server:\tSB TSPEED "); + logevent(telnet->logctx, "server:\tSB TSPEED "); break; case TELOPT_TTYPE: if (telnet->sb_len == 1 && telnet->sb_buf[0] == TELQUAL_SEND) { - char *logbuf; char *termtype = conf_get_str(telnet->conf, CONF_termtype); b = snewn(20 + strlen(termtype), unsigned char); b[0] = IAC; @@ -408,28 +396,24 @@ static void process_subneg(Telnet telnet) termtype[n]); b[n + 4] = IAC; b[n + 5] = SE; - telnet->bufsize = sk_write(telnet->s, (char *)b, n + 6); + telnet->bufsize = sk_write(telnet->s, b, n + 6); b[n + 4] = 0; - logevent(telnet->frontend, "server:\tSB TTYPE SEND"); - logbuf = dupprintf("client:\tSB TTYPE IS %s", b + 4); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + logevent(telnet->logctx, "server:\tSB TTYPE SEND"); + logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); sfree(b); } else - logevent(telnet->frontend, "server:\tSB TTYPE \r\n"); + logevent(telnet->logctx, "server:\tSB TTYPE \r\n"); break; case TELOPT_OLD_ENVIRON: case TELOPT_NEW_ENVIRON: p = telnet->sb_buf; q = p + telnet->sb_len; if (p < q && *p == TELQUAL_SEND) { - char *logbuf; p++; - logbuf = dupprintf("server:\tSB %s SEND", telopt(telnet->sb_opt)); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + logeventf(telnet->logctx, "server:\tSB %s SEND", + telopt(telnet->sb_opt)); if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { - if (conf_get_int(telnet->conf, CONF_rfc_environ)) { + if (conf_get_bool(telnet->conf, CONF_rfc_environ)) { value = RFC_VALUE; var = RFC_VAR; } else { @@ -498,31 +482,22 @@ static void process_subneg(Telnet telnet) } b[n++] = IAC; b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, (char *)b, n); + telnet->bufsize = sk_write(telnet->s, b, n); if (n == 6) { - logbuf = dupprintf("client:\tSB %s IS ", - telopt(telnet->sb_opt)); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + logeventf(telnet->logctx, "client:\tSB %s IS ", + telopt(telnet->sb_opt)); } else { - logbuf = dupprintf("client:\tSB %s IS:", - telopt(telnet->sb_opt)); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + 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)) { - logbuf = dupprintf("\t%s=%s", ekey, eval); - logevent(telnet->frontend, logbuf); - sfree(logbuf); - } - if (user) { - logbuf = dupprintf("\tUSER=%s", user); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + logeventf(telnet->logctx, "\t%s=%s", ekey, eval); } + if (user) + logeventf(telnet->logctx, "\tUSER=%s", user); } sfree(b); sfree(user); @@ -531,7 +506,7 @@ static void process_subneg(Telnet telnet) } } -static void do_telnet_read(Telnet telnet, char *buf, int len) +static void do_telnet_read(Telnet *telnet, char *buf, int len) { char *outbuf = NULL; int outbuflen = 0, outbufsize = 0; @@ -568,7 +543,7 @@ static void do_telnet_read(Telnet telnet, char *buf, int len) * just stop hiding on the next 0xf2 and hope for the best. */ else if (c == DM) - telnet->in_synch = 0; + telnet->in_synch = false; #endif if (c == CR && telnet->opt_states[o_they_bin.index] != ACTIVE) telnet->state = SEENCR; @@ -588,7 +563,7 @@ static void do_telnet_read(Telnet telnet, char *buf, int len) else if (c == SB) telnet->state = SEENSB; else if (c == DM) { - telnet->in_synch = 0; + telnet->in_synch = false; telnet->state = TOP_LEVEL; } else { /* ignore everything else; print it if it's IAC */ @@ -649,19 +624,19 @@ static void do_telnet_read(Telnet telnet, char *buf, int len) sfree(outbuf); } -static void telnet_log(Plug plug, int type, SockAddr addr, int port, +static void telnet_log(Plug *plug, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { - Telnet telnet = (Telnet) plug; - backend_socket_log(telnet->frontend, type, addr, 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); } -static int telnet_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void telnet_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { - Telnet telnet = (Telnet) plug; + Telnet *telnet = container_of(plug, Telnet, plug); /* * We don't implement independent EOF in each direction for Telnet @@ -673,33 +648,38 @@ static int telnet_closing(Plug plug, const char *error_msg, int error_code, sk_close(telnet->s); telnet->s = NULL; if (error_msg) - telnet->closed_on_socket_error = TRUE; - notify_remote_exit(telnet->frontend); + telnet->closed_on_socket_error = true; + seat_notify_remote_exit(telnet->seat); } if (error_msg) { - logevent(telnet->frontend, error_msg); - connection_fatal(telnet->frontend, "%s", error_msg); + logevent(telnet->logctx, error_msg); + seat_connection_fatal(telnet->seat, "%s", error_msg); } /* Otherwise, the remote side closed the connection normally. */ - return 0; } -static int telnet_receive(Plug plug, int urgent, char *data, int len) +static void telnet_receive(Plug *plug, int urgent, char *data, int len) { - Telnet telnet = (Telnet) plug; + Telnet *telnet = container_of(plug, Telnet, plug); if (urgent) - telnet->in_synch = TRUE; - telnet->session_started = TRUE; + telnet->in_synch = true; + telnet->session_started = true; do_telnet_read(telnet, data, len); - return 1; } -static void telnet_sent(Plug plug, int bufsize) +static void telnet_sent(Plug *plug, int bufsize) { - Telnet telnet = (Telnet) plug; + Telnet *telnet = container_of(plug, Telnet, plug); telnet->bufsize = bufsize; } +static const PlugVtable Telnet_plugvt = { + telnet_log, + telnet_closing, + telnet_receive, + telnet_sent +}; + /* * Called to set up the Telnet connection. * @@ -708,47 +688,44 @@ static void telnet_sent(Plug plug, int bufsize) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *telnet_init(void *frontend_handle, void **backend_handle, - Conf *conf, const char *host, int port, - char **realhost, int nodelay, int keepalive) +static const char *telnet_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, + char **realhost, bool nodelay, bool keepalive) { - static const struct plug_function_table fn_table = { - telnet_log, - telnet_closing, - telnet_receive, - telnet_sent - }; - SockAddr addr; + SockAddr *addr; const char *err; - Telnet telnet; + Telnet *telnet; char *loghost; int addressfamily; - telnet = snew(struct telnet_tag); - telnet->fn = &fn_table; + telnet = snew(Telnet); + telnet->plug.vt = &Telnet_plugvt; + telnet->backend.vt = &telnet_backend; telnet->conf = conf_copy(conf); telnet->s = NULL; - telnet->closed_on_socket_error = FALSE; - telnet->echoing = TRUE; - telnet->editing = TRUE; - telnet->activated = FALSE; + telnet->closed_on_socket_error = false; + telnet->echoing = true; + telnet->editing = true; + telnet->activated = false; telnet->sb_buf = NULL; telnet->sb_size = 0; - telnet->frontend = frontend_handle; + telnet->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; + 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->frontend, "Telnet connection"); + telnet->logctx, "Telnet connection"); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; @@ -760,17 +737,17 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, /* * Open socket. */ - telnet->s = new_connection(addr, *realhost, port, 0, 1, - nodelay, keepalive, (Plug) telnet, telnet->conf); + telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, + keepalive, &telnet->plug, telnet->conf); if ((err = sk_socket_error(telnet->s)) != NULL) return err; - telnet->pinger = pinger_new(telnet->conf, &telnet_backend, telnet); + telnet->pinger = pinger_new(telnet->conf, &telnet->backend); /* * Initialise option states. */ - if (conf_get_int(telnet->conf, CONF_passive_telnet)) { + if (conf_get_bool(telnet->conf, CONF_passive_telnet)) { const struct Opt *const *o; for (o = opts; *o; o++) @@ -783,18 +760,18 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, if (telnet->opt_states[(*o)->index] == REQUESTED) send_opt(telnet, (*o)->send, (*o)->option); } - telnet->activated = TRUE; + telnet->activated = true; } /* * Set up SYNCH state. */ - telnet->in_synch = FALSE; + telnet->in_synch = false; /* * We can send special commands from the start. */ - update_specials_menu(telnet->frontend); + seat_update_specials_menu(telnet->seat); /* * loghost overrides realhost, if specified. @@ -814,9 +791,9 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, return NULL; } -static void telnet_free(void *handle) +static void telnet_free(Backend *be) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); sfree(telnet->sb_buf); if (telnet->s) @@ -831,9 +808,9 @@ static void telnet_free(void *handle) * necessary, in this backend: we just save the fresh config for * any subsequent negotiations. */ -static void telnet_reconfig(void *handle, Conf *conf) +static void telnet_reconfig(Backend *be, Conf *conf) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); pinger_reconfig(telnet->pinger, telnet->conf, conf); conf_free(telnet->conf); telnet->conf = conf_copy(conf); @@ -842,9 +819,9 @@ static void telnet_reconfig(void *handle, Conf *conf) /* * Called to send data down the Telnet connection. */ -static int telnet_send(void *handle, const char *buf, int len) +static int telnet_send(Backend *be, const char *buf, int len) { - Telnet telnet = (Telnet) handle; + 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 }; @@ -862,11 +839,11 @@ static int telnet_send(void *handle, const char *buf, int len) while (p < end && iswritable(*p)) p++; - telnet->bufsize = sk_write(telnet->s, (char *)q, p - q); + telnet->bufsize = sk_write(telnet->s, q, p - q); while (p < end && !iswritable(*p)) { telnet->bufsize = - sk_write(telnet->s, (char *)(*p == IAC ? iac : cr), 2); + sk_write(telnet->s, *p == IAC ? iac : cr, 2); p++; } } @@ -877,21 +854,20 @@ static int telnet_send(void *handle, const char *buf, int len) /* * Called to query the current socket sendability status. */ -static int telnet_sendbuffer(void *handle) +static int telnet_sendbuffer(Backend *be) { - Telnet telnet = (Telnet) handle; + 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(void *handle, int width, int height) +static void telnet_size(Backend *be, int width, int height) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); unsigned char b[24]; int n; - char *logbuf; telnet->term_width = width; telnet->term_height = height; @@ -912,19 +888,17 @@ static void telnet_size(void *handle, int width, int height) if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */ b[n++] = IAC; b[n++] = SE; - telnet->bufsize = sk_write(telnet->s, (char *)b, n); - logbuf = dupprintf("client:\tSB NAWS %d,%d", - telnet->term_width, telnet->term_height); - logevent(telnet->frontend, logbuf); - sfree(logbuf); + 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(void *handle, Telnet_Special code) +static void telnet_special(Backend *be, SessionSpecialCode code, int arg) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); unsigned char b[2]; if (telnet->s == NULL) @@ -932,55 +906,55 @@ static void telnet_special(void *handle, Telnet_Special code) b[0] = IAC; switch (code) { - case TS_AYT: + case SS_AYT: b[1] = AYT; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_BRK: + case SS_BRK: b[1] = BREAK; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_EC: + case SS_EC: b[1] = EC; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_EL: + case SS_EL: b[1] = EL; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_GA: + case SS_GA: b[1] = GA; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_NOP: + case SS_NOP: b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_ABORT: + case SS_ABORT: b[1] = ABORT; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_AO: + case SS_AO: b[1] = AO; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_IP: + case SS_IP: b[1] = IP; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_SUSP: + case SS_SUSP: b[1] = SUSP; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_EOR: + case SS_EOR: b[1] = EOR; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_EOF: + case SS_EOF: b[1] = xEOF; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); break; - case TS_EOL: + 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) @@ -988,28 +962,15 @@ static void telnet_special(void *handle, Telnet_Special code) else telnet->bufsize = sk_write(telnet->s, "\r\n", 2); break; - case TS_SYNCH: + case SS_SYNCH: b[1] = DM; - telnet->bufsize = sk_write(telnet->s, (char *)b, 1); - telnet->bufsize = sk_write_oob(telnet->s, (char *)(b + 1), 1); - break; - case TS_RECHO: - if (telnet->opt_states[o_echo.index] == INACTIVE || - telnet->opt_states[o_echo.index] == REALLY_INACTIVE) { - telnet->opt_states[o_echo.index] = REQUESTED; - send_opt(telnet, o_echo.send, o_echo.option); - } + telnet->bufsize = sk_write(telnet->s, b, 1); + telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); break; - case TS_LECHO: - if (telnet->opt_states[o_echo.index] == ACTIVE) { - telnet->opt_states[o_echo.index] = REQUESTED; - send_opt(telnet, o_echo.nsend, o_echo.option); - } - break; - case TS_PING: + case SS_PING: if (telnet->opt_states[o_they_sga.index] == ACTIVE) { b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, (char *)b, 2); + telnet->bufsize = sk_write(telnet->s, b, 2); } break; default: @@ -1017,71 +978,66 @@ static void telnet_special(void *handle, Telnet_Special code) } } -static const struct telnet_special *telnet_get_specials(void *handle) +static const SessionSpecial *telnet_get_specials(Backend *be) { - static const struct telnet_special specials[] = { - {"Are You There", TS_AYT}, - {"Break", TS_BRK}, - {"Synch", TS_SYNCH}, - {"Erase Character", TS_EC}, - {"Erase Line", TS_EL}, - {"Go Ahead", TS_GA}, - {"No Operation", TS_NOP}, - {NULL, TS_SEP}, - {"Abort Process", TS_ABORT}, - {"Abort Output", TS_AO}, - {"Interrupt Process", TS_IP}, - {"Suspend Process", TS_SUSP}, - {NULL, TS_SEP}, - {"End Of Record", TS_EOR}, - {"End Of File", TS_EOF}, - {NULL, TS_EXITMENU} + 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 int telnet_connected(void *handle) +static bool telnet_connected(Backend *be) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); return telnet->s != NULL; } -static int telnet_sendok(void *handle) +static bool telnet_sendok(Backend *be) { - /* Telnet telnet = (Telnet) handle; */ - return 1; + /* Telnet *telnet = container_of(be, Telnet, backend); */ + return true; } -static void telnet_unthrottle(void *handle, int backlog) +static void telnet_unthrottle(Backend *be, int backlog) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); } -static int telnet_ldisc(void *handle, int option) +static bool telnet_ldisc(Backend *be, int option) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); if (option == LD_ECHO) return telnet->echoing; if (option == LD_EDIT) return telnet->editing; - return FALSE; + return false; } -static void telnet_provide_ldisc(void *handle, void *ldisc) +static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); telnet->ldisc = ldisc; } -static void telnet_provide_logctx(void *handle, void *logctx) -{ - /* This is a stub. */ -} - -static int telnet_exitcode(void *handle) +static int telnet_exitcode(Backend *be) { - Telnet telnet = (Telnet) handle; + Telnet *telnet = container_of(be, Telnet, backend); if (telnet->s != NULL) return -1; /* still connected */ else if (telnet->closed_on_socket_error) @@ -1094,12 +1050,12 @@ static int telnet_exitcode(void *handle) /* * cfg_info for Telnet does nothing at all. */ -static int telnet_cfg_info(void *handle) +static int telnet_cfg_info(Backend *be) { return 0; } -Backend telnet_backend = { +const struct BackendVtable telnet_backend = { telnet_init, telnet_free, telnet_reconfig, @@ -1113,7 +1069,6 @@ Backend telnet_backend = { telnet_sendok, telnet_ldisc, telnet_provide_ldisc, - telnet_provide_logctx, telnet_unthrottle, telnet_cfg_info, NULL /* test_for_upstream */, diff --git a/terminal.c b/terminal.c index c79944cd..013fcbc2 100644 --- a/terminal.c +++ b/terminal.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -21,8 +22,12 @@ #define posPlt(p1,p2) ( (p1).y <= (p2).y && (p1).x < (p2).x ) #define posPle(p1,p2) ( (p1).y <= (p2).y && (p1).x <= (p2).x ) -#define incpos(p) ( (p).x == term->cols ? ((p).x = 0, (p).y++, 1) : ((p).x++, 0) ) -#define decpos(p) ( (p).x == 0 ? ((p).x = term->cols, (p).y--, 1) : ((p).x--, 0) ) +#define incpos(p) ( (p).x == term->cols ? \ + ((p).x = 0, (p).y++, true) : \ + ((p).x++, false) ) +#define decpos(p) ( (p).x == 0 ? \ + ((p).x = term->cols, (p).y--, true) : \ + ((p).x--, false) ) #define VT52_PLUS @@ -100,19 +105,17 @@ 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 *, Context, int); -static void erase_lots(Terminal *, int, int, int); +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, int, int); +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, int); -#ifdef OPTIMISE_SCROLL -static void scroll_display(Terminal *, int, int, int); -#endif /* OPTIMISE_SCROLL */ +static void scroll(Terminal *, int, int, int, bool); +static void parse_optionalrgb(optionalrgb *out, unsigned *values); -static termline *newline(Terminal *term, int cols, int bce) +static termline *newline(Terminal *term, int cols, bool bce) { termline *line; int j; @@ -123,7 +126,7 @@ static termline *newline(Terminal *term, int cols, int bce) line->chars[j] = (bce ? term->erase_char : term->basic_erase_char); line->cols = line->size = cols; line->lattr = LATTR_NORM; - line->temporary = FALSE; + line->temporary = false; line->cc_free = 0; return line; @@ -169,7 +172,7 @@ static void cc_check(termline *line) j += line->chars[j].cc_next; assert(j >= line->cols && j < line->size); assert(!flags[j]); - flags[j] = TRUE; + flags[j] = true; } } @@ -178,7 +181,7 @@ static void cc_check(termline *line) while (1) { assert(j >= line->cols && j < line->size); assert(!flags[j]); - flags[j] = TRUE; + flags[j] = true; if (line->chars[j].cc_next) j += line->chars[j].cc_next; else @@ -279,26 +282,28 @@ static void clear_cc(termline *line, int col) * in do_paint() where we override what we expect the chr and attr * fields to be. */ -static int termchars_equal_override(termchar *a, termchar *b, - unsigned long bchr, unsigned long battr) +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; + return false; if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK)) - return FALSE; + 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 */ + 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 false; } - return TRUE; + return true; } -static int termchars_equal(termchar *a, termchar *b) +static bool termchars_equal(termchar *a, termchar *b) { return termchars_equal_override(a, b, b->chr, b->attr); } @@ -374,7 +379,8 @@ static void makerle(struct buf *b, termline *ldata, void (*makeliteral)(struct buf *b, termchar *c, unsigned long *state)) { - int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos, prev2; + int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos; + bool prev2; termchar *c = ldata->chars; unsigned long state = 0, oldstate; @@ -384,7 +390,7 @@ static void makerle(struct buf *b, termline *ldata, hdrsize = 0; add(b, 0); prevlen = prevpos = 0; - prev2 = FALSE; + prev2 = false; while (n-- > 0) { thispos = b->len; @@ -460,7 +466,7 @@ static void makerle(struct buf *b, termline *ldata, add(b, 0); /* And ensure this run doesn't interfere with the next. */ prevlen = prevpos = 0; - prev2 = FALSE; + prev2 = false; continue; } else { @@ -469,12 +475,12 @@ static void makerle(struct buf *b, termline *ldata, * identical, in case we find a third identical one * we want to turn into a run. */ - prev2 = TRUE; + prev2 = true; prevlen = thislen; prevpos = thispos; } } else { - prev2 = FALSE; + prev2 = false; prevlen = thislen; prevpos = thispos; } @@ -490,7 +496,7 @@ static void makerle(struct buf *b, termline *ldata, hdrsize = 0; add(b, 0); prevlen = prevpos = 0; - prev2 = FALSE; + prev2 = false; } } @@ -607,6 +613,24 @@ static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state) add(b, (unsigned char)(attr & 0xFF)); } } +static void makeliteral_truecolour(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * Put the used parts of the colour info into the buffer. + */ + add(b, ((c->truecolour.fg.enabled ? 1 : 0) | + (c->truecolour.bg.enabled ? 2 : 0))); + if (c->truecolour.fg.enabled) { + add(b, c->truecolour.fg.r); + add(b, c->truecolour.fg.g); + add(b, c->truecolour.fg.b); + } + if (c->truecolour.bg.enabled) { + add(b, c->truecolour.bg.r); + add(b, c->truecolour.bg.g); + add(b, c->truecolour.bg.b); + } +} static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state) { /* @@ -681,6 +705,7 @@ static unsigned char *compressline(termline *ldata) */ makerle(b, ldata, makeliteral_chr); makerle(b, ldata, makeliteral_attr); + makerle(b, ldata, makeliteral_truecolour); makerle(b, ldata, makeliteral_cc); /* @@ -826,6 +851,29 @@ static void readliteral_attr(struct buf *b, termchar *c, termline *ldata, c->attr = attr; } +static void readliteral_truecolour(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + int flags = get(b); + + if (flags & 1) { + c->truecolour.fg.enabled = true; + c->truecolour.fg.r = get(b); + c->truecolour.fg.g = get(b); + c->truecolour.fg.b = get(b); + } else { + c->truecolour.fg = optionalrgb_none; + } + + if (flags & 2) { + c->truecolour.bg.enabled = true; + c->truecolour.bg.r = get(b); + c->truecolour.bg.g = get(b); + c->truecolour.bg.b = get(b); + } else { + c->truecolour.bg = optionalrgb_none; + } +} static void readliteral_cc(struct buf *b, termchar *c, termline *ldata, unsigned long *state) { @@ -869,7 +917,7 @@ static termline *decompressline(unsigned char *data, int *bytes_used) ldata = snew(termline); ldata->chars = snewn(ncols, termchar); ldata->cols = ldata->size = ncols; - ldata->temporary = TRUE; + ldata->temporary = true; ldata->cc_free = 0; /* @@ -899,6 +947,7 @@ static termline *decompressline(unsigned char *data, int *bytes_used) */ readrle(b, ldata, readliteral_chr); readrle(b, ldata, readliteral_attr); + readrle(b, ldata, readliteral_truecolour); readrle(b, ldata, readliteral_cc); /* Return the number of bytes read, for diagnostic purposes. */ @@ -998,6 +1047,26 @@ static int sblines(Terminal *term) 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 @@ -1032,27 +1101,16 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) } if (whichtree == term->scrollback) { unsigned char *cline = index234(whichtree, treeindex); + if (!cline) + null_line_error(term, y, lineno, whichtree, treeindex, "cline"); line = decompressline(cline, NULL); } else { line = index234(whichtree, treeindex); } /* We assume that we don't screw up and retrieve something out of range. */ - if (line == NULL) { - fatalbox("line==NULL in terminal.c\n" - "lineno=%d y=%d w=%d h=%d\n" - "count(scrollback=%p)=%d\n" - "count(screen=%p)=%d\n" - "count(alt=%p)=%d alt_sblines=%d\n" - "whichtree=%p treeindex=%d\n\n" - "Please contact " - "and pass on the above information.", - lineno, y, term->cols, term->rows, - term->scrollback, count234(term->scrollback), - term->screen, count234(term->screen), - term->alt_screen, count234(term->alt_screen), term->alt_sblines, - whichtree, treeindex); - } + if (line == NULL) + null_line_error(term, y, lineno, whichtree, treeindex, "line"); assert(line != NULL); /* @@ -1078,8 +1136,8 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) return line; } -#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE) -#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) +#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 @@ -1103,25 +1161,25 @@ static void term_schedule_cblink(Terminal *term); static void term_timer(void *ctx, unsigned long now) { Terminal *term = (Terminal *)ctx; - int update = FALSE; + bool update = false; if (term->tblink_pending && now == term->next_tblink) { term->tblinker = !term->tblinker; - term->tblink_pending = FALSE; + term->tblink_pending = false; term_schedule_tblink(term); - update = TRUE; + update = true; } if (term->cblink_pending && now == term->next_cblink) { term->cblinker = !term->cblinker; - term->cblink_pending = FALSE; + term->cblink_pending = false; term_schedule_cblink(term); - update = TRUE; + update = true; } if (term->in_vbell && now == term->vbell_end) { - term->in_vbell = FALSE; - update = TRUE; + term->in_vbell = false; + update = true; } if (update || @@ -1132,7 +1190,7 @@ static void term_timer(void *ctx, unsigned long now) static void term_schedule_update(Terminal *term) { if (!term->window_update_pending) { - term->window_update_pending = TRUE; + term->window_update_pending = true; term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term); } } @@ -1143,7 +1201,7 @@ static void term_schedule_update(Terminal *term) */ static void seen_disp_event(Terminal *term) { - term->seen_disp_event = TRUE; /* for scrollback-reset-on-activity */ + term->seen_disp_event = true; /* for scrollback-reset-on-activity */ term_schedule_update(term); } @@ -1156,10 +1214,10 @@ 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; + term->tblink_pending = true; } else { - term->tblinker = 1; /* reset when not in use */ - term->tblink_pending = FALSE; + term->tblinker = true; /* reset when not in use */ + term->tblink_pending = false; } } @@ -1171,10 +1229,10 @@ 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; + term->cblink_pending = true; } else { - term->cblinker = 1; /* reset when not in use */ - term->cblink_pending = FALSE; + term->cblinker = true; /* reset when not in use */ + term->cblink_pending = false; } } @@ -1184,15 +1242,15 @@ static void term_schedule_cblink(Terminal *term) static void term_reset_cblink(Terminal *term) { seen_disp_event(term); - term->cblinker = 1; - term->cblink_pending = FALSE; + term->cblinker = true; + term->cblink_pending = false; term_schedule_cblink(term); } /* * Call to begin a visual bell. */ -static void term_schedule_vbell(Terminal *term, int already_started, +static void term_schedule_vbell(Terminal *term, bool already_started, long startpoint) { long ticks_already_gone; @@ -1203,11 +1261,11 @@ static void term_schedule_vbell(Terminal *term, int already_started, ticks_already_gone = 0; if (ticks_already_gone < VBELL_DELAY) { - term->in_vbell = TRUE; + term->in_vbell = true; term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone, term_timer, term); } else { - term->in_vbell = FALSE; + term->in_vbell = false; } } @@ -1217,7 +1275,7 @@ static void term_schedule_vbell(Terminal *term, int already_started, * position the cursor below the last non-blank line (scrolling if * necessary). */ -static void power_on(Terminal *term, int clear) +static void power_on(Terminal *term, bool clear) { term->alt_x = term->alt_y = 0; term->savecurs.x = term->savecurs.y = 0; @@ -1230,54 +1288,62 @@ static void power_on(Terminal *term, int clear) if (term->cols != -1) { int i; for (i = 0; i < term->cols; i++) - term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); + term->tabs[i] = (i % 8 == 0 ? true : false); } - term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om); - term->alt_ins = term->insert = FALSE; - term->alt_wnext = term->wrapnext = - term->save_wnext = term->alt_save_wnext = FALSE; - term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode); + 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 = term->utf = term->save_utf = term->alt_save_utf = 0; + term->alt_utf = false; + term->utf = false; + term->save_utf = false; + term->alt_save_utf = false; term->utf_state = 0; term->alt_sco_acs = term->sco_acs = term->save_sco_acs = term->alt_save_sco_acs = 0; term->cset_attr[0] = term->cset_attr[1] = term->save_csattr = term->alt_save_csattr = CSET_ASCII; - term->rvideo = 0; - term->in_vbell = FALSE; - term->cursor_on = 1; - term->big_cursor = 0; + term->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->term_editing = term->term_echoing = FALSE; - term->app_cursor_keys = conf_get_int(term->conf, CONF_app_cursor); - term->app_keypad_keys = conf_get_int(term->conf, CONF_app_keypad); - term->use_bce = conf_get_int(term->conf, CONF_bce); - term->blink_is_real = conf_get_int(term->conf, CONF_blinktext); + term->curr_truecolour.fg = term->curr_truecolour.bg = optionalrgb_none; + term->save_truecolour = term->alt_save_truecolour = term->curr_truecolour; + term->term_editing = term->term_echoing = false; + 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 = 0; - term->urxvt_extended_mouse = 0; - set_raw_mouse_mode(term->frontend, FALSE); - term->bracketed_paste = FALSE; + term->xterm_extended_mouse = false; + term->urxvt_extended_mouse = false; + win_set_raw_mouse_mode(term->win, false); + term->bracketed_paste = 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); + 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); + 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); + scroll(term, 0, term->rows - 1, 1, true); } } else { term->curs.y = 0; @@ -1292,24 +1358,22 @@ static void power_on(Terminal *term, int clear) */ void term_update(Terminal *term) { - Context ctx; - - term->window_update_pending = FALSE; + term->window_update_pending = false; - ctx = get_ctx(term->frontend); - if (ctx) { - int need_sbar_update = term->seen_disp_event; + if (win_setup_draw_ctx(term->win)) { + bool need_sbar_update = term->seen_disp_event; if (term->seen_disp_event && term->scroll_on_disp) { term->disptop = 0; /* return to main screen */ - term->seen_disp_event = 0; - need_sbar_update = TRUE; + term->seen_disp_event = false; + need_sbar_update = true; } if (need_sbar_update) update_sbar(term); - do_paint(term, ctx, TRUE); - sys_cursor(term->frontend, term->curs.x, term->curs.y - term->disptop); - free_ctx(ctx); + do_paint(term); + win_set_cursor_pos( + term->win, term->curs.x, term->curs.y - term->disptop); + win_free_draw_ctx(term->win); } } @@ -1326,7 +1390,7 @@ void term_seen_key_event(Terminal *term) * to be intended (e.g. beeps from filename completion * blocking repeatedly). */ - term->beep_overloaded = FALSE; + term->beep_overloaded = false; while (term->beephead) { struct beeptime *tmp = term->beephead; term->beephead = tmp->next; @@ -1347,7 +1411,7 @@ void term_seen_key_event(Terminal *term) /* * Same as power_on(), but an external function. */ -void term_pwron(Terminal *term, int clear) +void term_pwron(Terminal *term, bool clear) { power_on(term, clear); if (term->ldisc) /* cause ldisc to notice changes */ @@ -1360,9 +1424,11 @@ void term_pwron(Terminal *term, int clear) static void set_erase_char(Terminal *term) { term->erase_char = term->basic_erase_char; - if (term->use_bce) + if (term->use_bce) { term->erase_char.attr = (term->curr_attr & (ATTR_FGMASK | ATTR_BGMASK)); + term->erase_char.truecolour.bg = term->curr_truecolour.bg; + } } /* @@ -1373,44 +1439,46 @@ static void set_erase_char(Terminal *term) */ void term_copy_stuff_from_conf(Terminal *term) { - term->ansi_colour = conf_get_int(term->conf, CONF_ansi_colour); - term->arabicshaping = conf_get_int(term->conf, CONF_arabicshaping); + term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour); + term->arabicshaping = conf_get_bool(term->conf, CONF_arabicshaping); term->beep = conf_get_int(term->conf, CONF_beep); - term->bellovl = conf_get_int(term->conf, CONF_bellovl); + 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->bidi = conf_get_int(term->conf, CONF_bidi); - term->bksp_is_delete = conf_get_int(term->conf, CONF_bksp_is_delete); - term->blink_cur = conf_get_int(term->conf, CONF_blink_cur); - term->blinktext = conf_get_int(term->conf, CONF_blinktext); - term->cjk_ambig_wide = conf_get_int(term->conf, CONF_cjk_ambig_wide); + term->bidi = conf_get_bool(term->conf, CONF_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_int(term->conf, CONF_crhaslf); - term->erase_to_scrollback = conf_get_int(term->conf, CONF_erase_to_scrollback); + 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_int(term->conf, CONF_lfhascr); - term->logflush = conf_get_int(term->conf, CONF_logflush); + 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_int(term->conf, CONF_mouse_override); - term->nethack_keypad = conf_get_int(term->conf, CONF_nethack_keypad); - term->no_alt_screen = conf_get_int(term->conf, CONF_no_alt_screen); - term->no_applic_c = conf_get_int(term->conf, CONF_no_applic_c); - term->no_applic_k = conf_get_int(term->conf, CONF_no_applic_k); - term->no_dbackspace = conf_get_int(term->conf, CONF_no_dbackspace); - term->no_mouse_rep = conf_get_int(term->conf, CONF_no_mouse_rep); - term->no_remote_charset = conf_get_int(term->conf, CONF_no_remote_charset); - term->no_remote_resize = conf_get_int(term->conf, CONF_no_remote_resize); - term->no_remote_wintitle = conf_get_int(term->conf, CONF_no_remote_wintitle); - term->no_remote_clearscroll = conf_get_int(term->conf, CONF_no_remote_clearscroll); - term->rawcnp = conf_get_int(term->conf, CONF_rawcnp); - term->rect_select = conf_get_int(term->conf, CONF_rect_select); + 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_int(term->conf, CONF_rxvt_homeend); - term->scroll_on_disp = conf_get_int(term->conf, CONF_scroll_on_disp); - term->scroll_on_key = conf_get_int(term->conf, CONF_scroll_on_key); - term->xterm_256_colour = conf_get_int(term->conf, CONF_xterm_256_colour); + 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_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 @@ -1451,31 +1519,31 @@ void term_reconfig(Terminal *term, Conf *conf) * default one. The full list is: Auto wrap mode, DEC Origin * Mode, BCE, blinking text, character classes. */ - int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; + bool reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; int i; - reset_wrap = (conf_get_int(term->conf, CONF_wrap_mode) != - conf_get_int(conf, CONF_wrap_mode)); - reset_decom = (conf_get_int(term->conf, CONF_dec_om) != - conf_get_int(conf, CONF_dec_om)); - reset_bce = (conf_get_int(term->conf, CONF_bce) != - conf_get_int(conf, CONF_bce)); - reset_tblink = (conf_get_int(term->conf, CONF_blinktext) != - conf_get_int(conf, CONF_blinktext)); - reset_charclass = 0; + 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 = 1; + reset_charclass = true; /* * If the bidi or shaping settings have changed, flush the bidi * cache completely. */ - if (conf_get_int(term->conf, CONF_arabicshaping) != - conf_get_int(conf, CONF_arabicshaping) || - conf_get_int(term->conf, CONF_bidi) != - conf_get_int(conf, CONF_bidi)) { + if (conf_get_bool(term->conf, CONF_arabicshaping) != + conf_get_bool(conf, CONF_arabicshaping) || + conf_get_bool(term->conf, CONF_bidi) != + conf_get_bool(conf, CONF_bidi)) { for (i = 0; i < term->bidi_cache_size; i++) { sfree(term->pre_bidi_cache[i].chars); sfree(term->post_bidi_cache[i].chars); @@ -1490,30 +1558,30 @@ void term_reconfig(Terminal *term, Conf *conf) term->conf = conf_copy(conf); if (reset_wrap) - term->alt_wrap = term->wrap = conf_get_int(term->conf, CONF_wrap_mode); + term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); if (reset_decom) - term->alt_om = term->dec_om = conf_get_int(term->conf, CONF_dec_om); + term->alt_om = term->dec_om = conf_get_bool(term->conf, CONF_dec_om); if (reset_bce) { - term->use_bce = conf_get_int(term->conf, CONF_bce); + term->use_bce = conf_get_bool(term->conf, CONF_bce); set_erase_char(term); } if (reset_tblink) { - term->blink_is_real = conf_get_int(term->conf, CONF_blinktext); + 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_int(term->conf, CONF_no_alt_screen)) - swap_screen(term, 0, FALSE, FALSE); - if (conf_get_int(term->conf, CONF_no_mouse_rep)) { + if (conf_get_bool(term->conf, CONF_no_alt_screen)) + swap_screen(term, 0, false, false); + if (conf_get_bool(term->conf, CONF_no_mouse_rep)) { term->xterm_mouse = 0; - set_raw_mouse_mode(term->frontend, 0); + win_set_raw_mouse_mode(term->win, 0); } - if (conf_get_int(term->conf, CONF_no_remote_charset)) { + 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 = 0; + term->utf = false; } if (!conf_get_str(term->conf, CONF_printer)) { term_print_finish(term); @@ -1570,11 +1638,12 @@ void term_clrsb(Terminal *term) update_sbar(term); } +const optionalrgb optionalrgb_none = {0, 0, 0, 0}; + /* * Initialise the terminal. */ -Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, - void *frontend) +Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) { Terminal *term; @@ -1583,27 +1652,28 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, * that need it. */ term = snew(Terminal); - term->frontend = frontend; + 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->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->printing = term->only_printing = false; term->print_job = NULL; - term->vt52_mode = FALSE; - term->cr_lf_return = FALSE; - term->seen_disp_event = FALSE; - term->mouse_is_down = FALSE; - term->reset_132 = FALSE; - term->cblinker = term->tblinker = 0; - term->has_focus = 1; - term->repeat_off = FALSE; + term->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; @@ -1619,25 +1689,21 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, term->tabs = NULL; deselect(term); term->rows = term->cols = -1; - power_on(term, TRUE); + power_on(term, true); term->beephead = term->beeptail = NULL; -#ifdef OPTIMISE_SCROLL - term->scrollhead = term->scrolltail = NULL; -#endif /* OPTIMISE_SCROLL */ term->nbeeps = 0; - term->lastbeep = FALSE; - term->beep_overloaded = FALSE; + term->lastbeep = false; + term->beep_overloaded = false; term->attr_mask = 0xffffffff; - term->resize_fn = NULL; - term->resize_ctx = NULL; - term->in_term_out = FALSE; + 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_pending = false; term->bidi_cache_size = 0; term->pre_bidi_cache = term->post_bidi_cache = NULL; @@ -1646,8 +1712,20 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, 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; + return term; } @@ -1684,6 +1762,7 @@ void term_free(Terminal *term) 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); @@ -1724,7 +1803,7 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) if (newcols < 1) newcols = 1; deselect(term); - swap_screen(term, 0, FALSE, FALSE); + swap_screen(term, 0, false, false); term->alt_t = term->marg_t = 0; term->alt_b = term->marg_b = newrows - 1; @@ -1766,7 +1845,7 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) cline = delpos234(term->scrollback, --sblen); line = decompressline(cline, NULL); sfree(cline); - line->temporary = FALSE; /* reconstituted line is now real */ + line->temporary = false; /* reconstituted line is now real */ term->tempsblines -= 1; addpos234(term->screen, line, 0); term->curs.y += 1; @@ -1775,7 +1854,7 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) term->alt_savecurs.y += 1; } else { /* Add a new blank line at the bottom of the screen. */ - line = newline(term, newcols, FALSE); + line = newline(term, newcols, false); addpos234(term->screen, line, count234(term->screen)); } term->rows += 1; @@ -1817,7 +1896,7 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) /* Make a new displayed text buffer. */ newdisp = snewn(newrows, termline *); for (i = 0; i < newrows; i++) { - newdisp[i] = newline(term, newcols, FALSE); + newdisp[i] = newline(term, newcols, false); for (j = 0; j < newcols; j++) newdisp[i]->chars[j].attr = ATTR_INVALID; } @@ -1832,7 +1911,7 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) /* Make a new alternate screen. */ newalt = newtree234(NULL); for (i = 0; i < newrows; i++) { - line = newline(term, newcols, TRUE); + line = newline(term, newcols, true); addpos234(newalt, line, i); } if (term->alt_screen) { @@ -1847,7 +1926,7 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) { int i; for (i = (term->cols > 0 ? term->cols : 0); i < newcols; i++) - term->tabs[i] = (i % 8 == 0 ? TRUE : FALSE); + term->tabs[i] = (i % 8 == 0 ? true : false); } /* Check that the cursor positions are still valid. */ @@ -1876,32 +1955,29 @@ void term_size(Terminal *term, int newrows, int newcols, int newsavelines) if (term->alt_x >= newcols) term->alt_x = newcols - 1; term->alt_x = term->alt_y = 0; - term->wrapnext = term->alt_wnext = FALSE; + term->wrapnext = false; + term->alt_wnext = false; term->rows = newrows; term->cols = newcols; term->savelines = newsavelines; - swap_screen(term, save_alt_which, FALSE, FALSE); + swap_screen(term, save_alt_which, false, false); update_sbar(term); term_update(term); - if (term->resize_fn) - term->resize_fn(term->resize_ctx, term->cols, term->rows); + if (term->backend) + backend_size(term->backend, term->cols, term->rows); } /* - * Hand a function and context pointer to the terminal which it can - * use to notify a back end of resizes. + * Hand a backend to the terminal, so it can be notified of resizes. */ -void term_provide_resize_fn(Terminal *term, - void (*resize_fn)(void *, int, int), - void *resize_ctx) +void term_provide_backend(Terminal *term, Backend *backend) { - term->resize_fn = resize_fn; - term->resize_ctx = resize_ctx; - if (resize_fn && term->cols > 0 && term->rows > 0) - resize_fn(resize_ctx, term->cols, term->rows); + 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. @@ -1923,20 +1999,23 @@ static int find_last_nonempty_line(Terminal * term, tree234 * screen) } /* - * Swap screens. If `reset' is TRUE and we have been asked to + * Swap screens. If `reset' is true and we have been asked to * switch to the alternate screen, we must bring most of its * configuration from the main screen and erase the contents of the * alternate screen completely. (This is even true if we're already * on it! Blame xterm.) */ -static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) +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 */ + reset = false; /* do no weird resetting if which==0 */ if (which != term->alt_which) { term->alt_which = which; @@ -1959,24 +2038,24 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) t = term->marg_b; if (!reset) term->marg_b = term->alt_b; term->alt_b = t; - t = term->dec_om; + bt = term->dec_om; if (!reset) term->dec_om = term->alt_om; - term->alt_om = t; - t = term->wrap; + term->alt_om = bt; + bt = term->wrap; if (!reset) term->wrap = term->alt_wrap; - term->alt_wrap = t; - t = term->wrapnext; + term->alt_wrap = bt; + bt = term->wrapnext; if (!reset) term->wrapnext = term->alt_wnext; - term->alt_wnext = t; - t = term->insert; + term->alt_wnext = bt; + bt = term->insert; if (!reset) term->insert = term->alt_ins; - term->alt_ins = t; + term->alt_ins = bt; t = term->cset; if (!reset) term->cset = term->alt_cset; term->alt_cset = t; - t = term->utf; + bt = term->utf; if (!reset) term->utf = term->alt_utf; - term->alt_utf = t; + term->alt_utf = bt; t = term->sco_acs; if (!reset) term->sco_acs = term->alt_sco_acs; term->alt_sco_acs = t; @@ -1997,14 +2076,18 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) if (!reset && !keep_cur_pos) term->save_attr = term->alt_save_attr; term->alt_save_attr = t; - t = term->save_utf; + ttc = term->save_truecolour; + if (!reset && !keep_cur_pos) + term->save_truecolour = term->alt_save_truecolour; + term->alt_save_truecolour = ttc; + bt = term->save_utf; if (!reset && !keep_cur_pos) term->save_utf = term->alt_save_utf; - term->alt_save_utf = t; - t = term->save_wnext; + term->alt_save_utf = bt; + bt = term->save_wnext; if (!reset && !keep_cur_pos) term->save_wnext = term->alt_save_wnext; - term->alt_save_wnext = t; + term->alt_save_wnext = bt; t = term->save_sco_acs; if (!reset && !keep_cur_pos) term->save_sco_acs = term->alt_save_sco_acs; @@ -2015,7 +2098,7 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) /* * Yes, this _is_ supposed to honour background-colour-erase. */ - erase_lots(term, FALSE, TRUE, TRUE); + erase_lots(term, false, true, true); } } @@ -2025,8 +2108,8 @@ static void swap_screen(Terminal *term, int which, int reset, int keep_cur_pos) static void update_sbar(Terminal *term) { int nscroll = sblines(term); - set_sbar(term->frontend, nscroll + term->rows, - nscroll + term->disptop, term->rows); + win_set_scrollbar(term->win, nscroll + term->rows, + nscroll + term->disptop, term->rows); } /* @@ -2041,24 +2124,17 @@ static void check_selection(Terminal *term, pos from, pos to) /* * Scroll the screen. (`lines' is +ve for scrolling forward, -ve - * for backward.) `sb' is TRUE if the scrolling is permitted to + * for backward.) `sb' is true if the scrolling is permitted to * affect the scrollback buffer. */ -static void scroll(Terminal *term, int topline, int botline, int lines, int sb) +static void scroll(Terminal *term, int topline, int botline, + int lines, bool sb) { termline *line; int i, seltop, scrollwinsize; -#ifdef OPTIMISE_SCROLL - int olddisptop, shift; -#endif /* OPTIMISE_SCROLL */ if (topline != 0 || term->alt_which != 0) - sb = FALSE; - -#ifdef OPTIMISE_SCROLL - olddisptop = term->disptop; - shift = lines; -#endif /* OPTIMISE_SCROLL */ + sb = false; scrollwinsize = botline - topline + 1; @@ -2180,81 +2256,8 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb) } } } -#ifdef OPTIMISE_SCROLL - shift += term->disptop - olddisptop; - if (shift < term->rows && shift > -term->rows && shift != 0) - scroll_display(term, topline, botline, shift); -#endif /* OPTIMISE_SCROLL */ -} - -#ifdef OPTIMISE_SCROLL -/* - * Add a scroll of a region on the screen into the pending scroll list. - * `lines' is +ve for scrolling forward, -ve for backward. - * - * If the scroll is on the same area as the last scroll in the list, - * merge them. - */ -static void save_scroll(Terminal *term, int topline, int botline, int lines) -{ - struct scrollregion *newscroll; - if (term->scrolltail && - term->scrolltail->topline == topline && - term->scrolltail->botline == botline) { - term->scrolltail->lines += lines; - } else { - newscroll = snew(struct scrollregion); - newscroll->topline = topline; - newscroll->botline = botline; - newscroll->lines = lines; - newscroll->next = NULL; - - if (!term->scrollhead) - term->scrollhead = newscroll; - else - term->scrolltail->next = newscroll; - term->scrolltail = newscroll; - } } -/* - * Scroll the physical display, and our conception of it in disptext. - */ -static void scroll_display(Terminal *term, int topline, int botline, int lines) -{ - int distance, nlines, i, j; - - distance = lines > 0 ? lines : -lines; - nlines = botline - topline + 1 - distance; - if (lines > 0) { - for (i = 0; i < nlines; i++) - for (j = 0; j < term->cols; j++) - copy_termchar(term->disptext[i], j, - term->disptext[i+distance]->chars+j); - if (term->dispcursy >= 0 && - term->dispcursy >= topline + distance && - term->dispcursy < topline + distance + nlines) - term->dispcursy -= distance; - for (i = 0; i < distance; i++) - for (j = 0; j < term->cols; j++) - term->disptext[nlines+i]->chars[j].attr |= ATTR_INVALID; - } else { - for (i = nlines; i-- ;) - for (j = 0; j < term->cols; j++) - copy_termchar(term->disptext[i+distance], j, - term->disptext[i]->chars+j); - if (term->dispcursy >= 0 && - term->dispcursy >= topline && - term->dispcursy < topline + nlines) - term->dispcursy += distance; - for (i = 0; i < distance; i++) - for (j = 0; j < term->cols; j++) - term->disptext[i]->chars[j].attr |= ATTR_INVALID; - } - save_scroll(term, topline, botline, lines); -} -#endif /* OPTIMISE_SCROLL */ - /* * Move the cursor to a given position, clipping at boundaries. We * may or may not want to clip at the scroll margin: marg_clip is 0 @@ -2281,17 +2284,18 @@ static void move(Terminal *term, int x, int y, int marg_clip) y = term->rows - 1; term->curs.x = x; term->curs.y = y; - term->wrapnext = FALSE; + term->wrapnext = false; } /* * Save or restore the cursor and SGR mode. */ -static void save_cursor(Terminal *term, int save) +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; @@ -2306,6 +2310,7 @@ static void save_cursor(Terminal *term, int save) 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; @@ -2314,7 +2319,7 @@ static void save_cursor(Terminal *term, int save) * longer at the rightmost edge. */ if (term->wrapnext && term->curs.x < term->cols-1) - term->wrapnext = FALSE; + term->wrapnext = false; term->cset_attr[term->cset] = term->save_csattr; term->sco_acs = term->save_sco_acs; set_erase_char(term); @@ -2367,24 +2372,24 @@ static void check_boundary(Terminal *term, int x, int y) * whole line, or parts thereof. */ static void erase_lots(Terminal *term, - int line_only, int from_begin, int to_end) + bool line_only, bool from_begin, bool to_end) { pos start, end; - int erase_lattr; - int erasing_lines_from_top = 0; + 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; + erase_lattr = false; } else { start.y = 0; start.x = 0; end.y = term->rows; end.x = 0; - erase_lattr = TRUE; + erase_lattr = true; } if (!from_begin) { start = term->curs; @@ -2404,7 +2409,7 @@ static void erase_lots(Terminal *term, /* Lines scrolled away shouldn't be brought back on if the terminal * resizes. */ if (start.y == 0 && start.x == 0 && end.x == 0 && erase_lattr) - erasing_lines_from_top = 1; + 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 @@ -2416,7 +2421,7 @@ static void erase_lots(Terminal *term, scrolllines = find_last_nonempty_line(term, term->screen) + 1; } if (scrolllines > 0) - scroll(term, 0, scrolllines - 1, scrolllines, TRUE); + scroll(term, 0, scrolllines - 1, scrolllines, true); } else { termline *ldata = scrlineptr(start.y); while (poslt(start, end)) { @@ -2437,7 +2442,7 @@ static void erase_lots(Terminal *term, /* After an erase of lines from the top of the screen, we shouldn't * bring the lines back again if the terminal enlarges (since the user or - * application has explictly thrown them away). */ + * application has explicitly thrown them away). */ if (erasing_lines_from_top && !(term->alt_which)) term->tempsblines = 0; } @@ -2520,9 +2525,9 @@ static void insch(Terminal *term, int n) * Toggle terminal mode `mode' to state `state'. (`query' indicates * whether the mode is a DEC private one or a normal one.) */ -static void toggle_mode(Terminal *term, int mode, int query, int state) +static void toggle_mode(Terminal *term, int mode, int query, bool state) { - if (query) + if (query == 1) { switch (mode) { case 1: /* DECCKM: application cursor keys */ term->app_cursor_keys = state; @@ -2530,8 +2535,8 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 2: /* DECANM: VT52 mode */ term->vt52_mode = !state; if (term->vt52_mode) { - term->blink_is_real = FALSE; - term->vt52_bold = FALSE; + term->blink_is_real = false; + term->vt52_bold = false; } else { term->blink_is_real = term->blinktext; } @@ -2540,12 +2545,12 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 3: /* DECCOLM: 80/132 columns */ deselect(term); if (!term->no_remote_resize) - request_resize(term->frontend, state ? 132 : 80, term->rows); + win_request_resize(term->win, state ? 132 : 80, term->rows); term->reset_132 = state; term->alt_t = term->marg_t = 0; term->alt_b = term->marg_b = term->rows - 1; move(term, 0, 0, 0); - erase_lots(term, FALSE, TRUE, TRUE); + erase_lots(term, false, true, true); break; case 5: /* DECSCNM: reverse video */ /* @@ -2556,7 +2561,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) */ if (term->rvideo && !state) { /* This is an OFF, so set up a vbell */ - term_schedule_vbell(term, TRUE, term->rvbell_startpoint); + 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(); @@ -2586,28 +2591,28 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 47: /* alternate screen */ compatibility(OTHER); deselect(term); - swap_screen(term, term->no_alt_screen ? 0 : state, FALSE, FALSE); + 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; - set_raw_mouse_mode(term->frontend, state); + win_set_raw_mouse_mode(term->win, state); break; case 1002: /* xterm mouse 2 (inc. button drags) */ term->xterm_mouse = state ? 2 : 0; - set_raw_mouse_mode(term->frontend, state); + win_set_raw_mouse_mode(term->win, state); break; case 1006: /* xterm extended mouse */ - term->xterm_extended_mouse = state ? 1 : 0; + term->xterm_extended_mouse = state; break; case 1015: /* urxvt extended mouse */ - term->urxvt_extended_mouse = state ? 1 : 0; + 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); + swap_screen(term, term->no_alt_screen ? 0 : state, true, true); if (term->scroll_on_disp) term->disptop = 0; break; @@ -2622,16 +2627,17 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) if (!state) seen_disp_event(term); compatibility(OTHER); deselect(term); - swap_screen(term, term->no_alt_screen ? 0 : state, TRUE, FALSE); + 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; + term->bracketed_paste = state ? true : false; break; - } else + } + } else if (query == 0) { switch (mode) { case 4: /* IRM: set insert mode */ compatibility(VT102); @@ -2649,6 +2655,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) compatibility2(OTHER, VT220); term->big_cursor = !state; } + } } /* @@ -2666,15 +2673,32 @@ static void do_osc(Terminal *term) case 0: case 1: if (!term->no_remote_wintitle) - set_icon(term->frontend, term->osc_string); + win_set_icon_title(term->win, term->osc_string); if (term->esc_args[0] == 1) break; /* fall through: parameter 0 means set both */ case 2: case 21: if (!term->no_remote_wintitle) - set_title(term->frontend, term->osc_string); + win_set_title(term->win, term->osc_string); break; + case 4: + if (term->ldisc && !strcmp(term->osc_string, "?")) { + int r, g, b; + if (win_palette_get(term->win, toint(term->esc_args[1]), + &r, &g, &b)) { + char *reply_buf = dupprintf( + "\033]4;%u;rgb:%04x/%04x/%04x\007", + term->esc_args[1], + (unsigned)r * 0x0101, + (unsigned)g * 0x0101, + (unsigned)b * 0x0101); + ldisc_send(term->ldisc, reply_buf, strlen(reply_buf), + false); + sfree(reply_buf); + } + } + break; } } } @@ -2723,7 +2747,144 @@ static void term_print_finish(Terminal *term) } printer_finish_job(term->print_job); term->print_job = NULL; - term->printing = term->only_printing = FALSE; + 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->cjk_ambig_wide ? + mk_wcwidth_cjk((unsigned int) c) : + mk_wcwidth((unsigned int) c)); + + if (term->wrapnext && term->wrap && width > 0) { + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = false; + cline = scrlineptr(term->curs.y); + } + if (term->insert && width > 0) + insch(term, width); + if (term->selstate != NO_SELECTION) { + pos cursplus = term->curs; + incpos(cursplus); + check_selection(term, term->curs, cursplus); + } + if (((c & CSET_MASK) == CSET_ASCII || + (c & CSET_MASK) == 0) && term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); + + switch (width) { + case 2: + /* + * If we're about to display a double-width character starting + * in the rightmost column, then we do something special + * instead. We must print a space in the last column of the + * screen, then wrap; and we also set LATTR_WRAPPED2 which + * instructs subsequent cut-and-pasting not only to splice + * this line to the one after it, but to ignore the space in + * the last character position as well. (Because what was + * actually output to the terminal was presumably just a + * sequence of CJK characters, and we don't want a space to be + * pasted in the middle of those just because they had the + * misfortune to start in the wrong parity column. xterm + * concurs.) + */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + if (term->curs.x == term->cols-1) { + copy_termchar(cline, term->curs.x, + &term->erase_char); + cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, + 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + cline = scrlineptr(term->curs.y); + /* Now we must check_boundary again, of course. */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + } + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = c; + cline->chars[term->curs.x].attr = term->curr_attr; + 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 == term->cols) { + term->curs.x--; + term->wrapnext = true; + if (term->wrap && term->vt52_mode) { + cline->lattr |= LATTR_WRAPPED; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + term->wrapnext = false; + } + } + seen_disp_event(term); } /* @@ -2813,6 +2974,10 @@ static void term_out(Terminal *term) /* UTF-8 must be stateless so we ignore iso2022. */ if (term->ucsdata->unitab_ctrl[c] != 0xFF) c = term->ucsdata->unitab_ctrl[c]; + else if ((term->utf8linedraw) && + (term->cset_attr[term->cset] == CSET_LINEDRW)) + /* Linedraw characters are explicitly enabled */ + c = ((unsigned char) c) | CSET_LINEDRW; else c = ((unsigned char)c) | CSET_ASCII; break; } else if ((c & 0xe0) == 0xc0) { @@ -2941,7 +3106,7 @@ static void term_out(Terminal *term) c = 0; else { term->termstate = SEEN_ESC; - term->esc_query = FALSE; + term->esc_query = 0; c = '@' + (c & 0x1F); } } @@ -2950,7 +3115,7 @@ static void term_out(Terminal *term) if (c == '\177' && term->termstate < DO_CTRLS && has_compat(OTHER)) { if (term->curs.x && !term->wrapnext) term->curs.x--; - term->wrapnext = FALSE; + term->wrapnext = false; /* destructive backspace might be disabled */ if (!term->no_dbackspace) { check_boundary(term, term->curs.x, term->curs.y); @@ -2973,7 +3138,7 @@ static void term_out(Terminal *term) compatibility(ANSIMIN); if (term->ldisc) { lpage_send(term->ldisc, DEFAULT_CODEPAGE, - term->answerback, term->answerbacklen, 0); + term->answerback, term->answerbacklen, false); } break; case '\007': /* BEL: Bell */ @@ -3016,7 +3181,7 @@ static void term_out(Terminal *term) * last beep was more than s seconds ago, * leave overload mode. */ - term->beep_overloaded = FALSE; + term->beep_overloaded = false; } else if (term->bellovl && !term->beep_overloaded && term->nbeeps >= term->bellovl_n) { /* @@ -3024,7 +3189,7 @@ static void term_out(Terminal *term) * remaining in the queue, go into overload * mode. */ - term->beep_overloaded = TRUE; + term->beep_overloaded = true; } term->lastbeep = ticks; @@ -3032,23 +3197,22 @@ static void term_out(Terminal *term) * Perform an actual beep if we're not overloaded. */ if (!term->bellovl || !term->beep_overloaded) { - do_beep(term->frontend, term->beep); + win_bell(term->win, term->beep); if (term->beep == BELL_VISUAL) { - term_schedule_vbell(term, FALSE, 0); + term_schedule_vbell(term, false, 0); } } seen_disp_event(term); } break; case '\b': /* BS: Back space */ - if (term->curs.x == 0 && - (term->curs.y == 0 || term->wrap == 0)) + 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; + term->wrapnext = false; else term->curs.x--; seen_disp_event(term); @@ -3067,17 +3231,17 @@ static void term_out(Terminal *term) else { compatibility(ANSIMIN); term->termstate = SEEN_ESC; - term->esc_query = FALSE; + term->esc_query = 0; } break; case '\015': /* CR: Carriage return */ term->curs.x = 0; - term->wrapnext = FALSE; + 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); + scroll(term, term->marg_t, term->marg_b, 1, true); else if (term->curs.y < term->rows - 1) term->curs.y++; } @@ -3087,10 +3251,10 @@ static void term_out(Terminal *term) case '\014': /* FF: Form feed */ if (has_compat(SCOANSI)) { move(term, 0, 0, 0); - erase_lots(term, FALSE, FALSE, TRUE); + erase_lots(term, false, false, true); if (term->scroll_on_disp) term->disptop = 0; - term->wrapnext = FALSE; + term->wrapnext = false; seen_disp_event(term); break; } @@ -3098,12 +3262,12 @@ static void term_out(Terminal *term) compatibility(VT100); case '\012': /* LF: Line feed */ if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, 1, TRUE); + 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; + term->wrapnext = false; seen_disp_event(term); if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); @@ -3136,142 +3300,8 @@ static void term_out(Terminal *term) case TOPLEVEL: /* Only graphic characters get this far; * ctrls are stripped above */ - { - termline *cline = scrlineptr(term->curs.y); - int width = 0; - if (DIRECT_CHAR(c)) - width = 1; - if (!width) - width = (term->cjk_ambig_wide ? - mk_wcwidth_cjk((unsigned int) c) : - mk_wcwidth((unsigned int) c)); - - if (term->wrapnext && term->wrap && width > 0) { - cline->lattr |= LATTR_WRAPPED; - if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, 1, TRUE); - else if (term->curs.y < term->rows - 1) - term->curs.y++; - term->curs.x = 0; - term->wrapnext = FALSE; - cline = scrlineptr(term->curs.y); - } - if (term->insert && width > 0) - insch(term, width); - if (term->selstate != NO_SELECTION) { - pos cursplus = term->curs; - incpos(cursplus); - check_selection(term, term->curs, cursplus); - } - if (((c & CSET_MASK) == CSET_ASCII || - (c & CSET_MASK) == 0) && - term->logctx) - logtraffic(term->logctx, (unsigned char) c, - LGTYP_ASCII); - - switch (width) { - case 2: - /* - * If we're about to display a double-width - * character starting in the rightmost - * column, then we do something special - * instead. We must print a space in the - * last column of the screen, then wrap; - * and we also set LATTR_WRAPPED2 which - * instructs subsequent cut-and-pasting not - * only to splice this line to the one - * after it, but to ignore the space in the - * last character position as well. - * (Because what was actually output to the - * terminal was presumably just a sequence - * of CJK characters, and we don't want a - * space to be pasted in the middle of - * those just because they had the - * misfortune to start in the wrong parity - * column. xterm concurs.) - */ - check_boundary(term, term->curs.x, term->curs.y); - check_boundary(term, term->curs.x+2, term->curs.y); - if (term->curs.x == term->cols-1) { - copy_termchar(cline, term->curs.x, - &term->erase_char); - cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2; - if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, - 1, TRUE); - else if (term->curs.y < term->rows - 1) - term->curs.y++; - term->curs.x = 0; - cline = scrlineptr(term->curs.y); - /* Now we must check_boundary again, of course. */ - check_boundary(term, term->curs.x, term->curs.y); - check_boundary(term, term->curs.x+2, term->curs.y); - } - - /* FULL-TERMCHAR */ - clear_cc(cline, term->curs.x); - cline->chars[term->curs.x].chr = c; - cline->chars[term->curs.x].attr = term->curr_attr; - - term->curs.x++; - - /* FULL-TERMCHAR */ - clear_cc(cline, term->curs.x); - cline->chars[term->curs.x].chr = UCSWIDE; - cline->chars[term->curs.x].attr = term->curr_attr; - - break; - case 1: - check_boundary(term, term->curs.x, term->curs.y); - check_boundary(term, term->curs.x+1, term->curs.y); - - /* FULL-TERMCHAR */ - clear_cc(cline, term->curs.x); - cline->chars[term->curs.x].chr = c; - cline->chars[term->curs.x].attr = term->curr_attr; - - break; - case 0: - if (term->curs.x > 0) { - int x = term->curs.x - 1; - - /* If we're in wrapnext state, the character - * to combine with is _here_, not to our left. */ - if (term->wrapnext) - x++; - - /* - * If the previous character is - * UCSWIDE, back up another one. - */ - if (cline->chars[x].chr == UCSWIDE) { - assert(x > 0); - x--; - } - - add_cc(cline, x, c); - seen_disp_event(term); - } - continue; - default: - continue; - } - term->curs.x++; - if (term->curs.x == term->cols) { - term->curs.x--; - term->wrapnext = TRUE; - if (term->wrap && term->vt52_mode) { - cline->lattr |= LATTR_WRAPPED; - if (term->curs.y == term->marg_b) - scroll(term, term->marg_t, term->marg_b, 1, TRUE); - else if (term->curs.y < term->rows - 1) - term->curs.y++; - term->curs.x = 0; - term->wrapnext = FALSE; - } - } - seen_disp_event(term); - } + term_display_graphic_char(term, c); + term->last_graphic_char = c; break; case OSC_MAYBE_ST: @@ -3300,74 +3330,75 @@ static void term_out(Terminal *term) term->termstate = SEEN_CSI; term->esc_nargs = 1; term->esc_args[0] = ARG_DEFAULT; - term->esc_query = FALSE; + 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); + save_cursor(term, true); break; case '8': /* DECRC: restore cursor */ compatibility(VT100); - save_cursor(term, FALSE); + save_cursor(term, false); seen_disp_event(term); break; case '=': /* DECKPAM: Keypad application mode */ compatibility(VT100); - term->app_keypad_keys = TRUE; + term->app_keypad_keys = true; break; case '>': /* DECKPNM: Keypad numeric mode */ compatibility(VT100); - term->app_keypad_keys = FALSE; + 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); + scroll(term, term->marg_t, term->marg_b, 1, true); else if (term->curs.y < term->rows - 1) term->curs.y++; - term->wrapnext = FALSE; + 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); + scroll(term, term->marg_t, term->marg_b, 1, true); else if (term->curs.y < term->rows - 1) term->curs.y++; - term->wrapnext = FALSE; + 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); + scroll(term, term->marg_t, term->marg_b, -1, true); else if (term->curs.y > 0) term->curs.y--; - term->wrapnext = FALSE; + term->wrapnext = false; seen_disp_event(term); break; case 'Z': /* DECID: terminal type query */ compatibility(VT100); - if (term->ldisc) + if (term->ldisc && term->id_string[0]) ldisc_send(term->ldisc, term->id_string, - strlen(term->id_string), 0); + strlen(term->id_string), false); break; case 'c': /* RIS: restore power-on settings */ compatibility(VT100); - power_on(term, TRUE); + 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) - request_resize(term->frontend, 80, term->rows); - term->reset_132 = 0; + win_request_resize(term->win, 80, term->rows); + term->reset_132 = false; } if (term->scroll_on_disp) term->disptop = 0; @@ -3375,7 +3406,7 @@ static void term_out(Terminal *term) break; case 'H': /* HTS: set a tab */ compatibility(VT100); - term->tabs[term->curs.x] = TRUE; + term->tabs[term->curs.x] = true; break; case ANSI('8', '#'): /* DECALN: fills screen with Es :-) */ @@ -3480,12 +3511,12 @@ static void term_out(Terminal *term) case ANSI('G', '%'): compatibility(OTHER); if (!term->no_remote_charset) - term->utf = 1; + term->utf = true; break; case ANSI('@', '%'): compatibility(OTHER); if (!term->no_remote_charset) - term->utf = 0; + term->utf = false; break; } break; @@ -3514,7 +3545,7 @@ static void term_out(Terminal *term) if (term->esc_query) term->esc_query = -1; else if (c == '?') - term->esc_query = TRUE; + term->esc_query = 1; else term->esc_query = c; term->termstate = SEEN_CSI; @@ -3536,12 +3567,22 @@ static void term_out(Terminal *term) 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); + { + 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, 0); + ldisc_send(term->ldisc, "\033[>0;136;0c", 11, + false); break; case 'a': /* HPR: move right N cols */ compatibility(ANSI); @@ -3613,7 +3654,7 @@ static void term_out(Terminal *term) i++; if (i > 3) i = 0; - erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); + erase_lots(term, false, !!(i & 2), !!(i & 1)); } } if (term->scroll_on_disp) @@ -3625,7 +3666,7 @@ static void term_out(Terminal *term) unsigned int i = def(term->esc_args[0], 0) + 1; if (i > 3) i = 0; - erase_lots(term, TRUE, !!(i & 2), !!(i & 1)); + erase_lots(term, true, !!(i & 2), !!(i & 1)); } seen_disp_event(term); break; @@ -3634,7 +3675,7 @@ static void term_out(Terminal *term) 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); + -def(term->esc_args[0], 1), false); seen_disp_event(term); break; case 'M': /* DL: delete lines */ @@ -3643,7 +3684,7 @@ static void term_out(Terminal *term) if (term->curs.y <= term->marg_b) scroll(term, term->curs.y, term->marg_b, def(term->esc_args[0], 1), - TRUE); + true); seen_disp_event(term); break; case '@': /* ICH: insert chars */ @@ -3662,9 +3703,9 @@ static void term_out(Terminal *term) case 'c': /* DA: terminal type query */ compatibility(VT100); /* This is the response for a VT102 */ - if (term->ldisc) + if (term->ldisc && term->id_string[0]) ldisc_send(term->ldisc, term->id_string, - strlen(term->id_string), 0); + strlen(term->id_string), false); break; case 'n': /* DSR: cursor position query */ if (term->ldisc) { @@ -3672,9 +3713,10 @@ static void term_out(Terminal *term) char buf[32]; sprintf(buf, "\033[%d;%dR", term->curs.y + 1, term->curs.x + 1); - ldisc_send(term->ldisc, buf, strlen(buf), 0); + ldisc_send(term->ldisc, buf, strlen(buf), + false); } else if (term->esc_args[0] == 5) { - ldisc_send(term->ldisc, "\033[0n", 4, 0); + ldisc_send(term->ldisc, "\033[0n", 4, false); } } break; @@ -3685,7 +3727,7 @@ static void term_out(Terminal *term) int i; for (i = 0; i < term->esc_nargs; i++) toggle_mode(term, term->esc_args[i], - term->esc_query, TRUE); + term->esc_query, true); } break; case 'i': /* MC: Media copy */ @@ -3697,7 +3739,7 @@ static void term_out(Terminal *term) if (term->esc_args[0] == 5 && (printer = conf_get_str(term->conf, CONF_printer))[0]) { - term->printing = TRUE; + term->printing = true; term->only_printing = !term->esc_query; term->print_state = 0; term_print_setup(term, printer); @@ -3714,18 +3756,18 @@ static void term_out(Terminal *term) int i; for (i = 0; i < term->esc_nargs; i++) toggle_mode(term, term->esc_args[i], - term->esc_query, FALSE); + 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; + 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; + term->tabs[i] = false; } } break; @@ -3799,11 +3841,17 @@ static void term_out(Terminal *term) 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 */ @@ -3816,7 +3864,7 @@ static void term_out(Terminal *term) break; case 6: /* SCO light bkgrd */ compatibility(SCOANSI); - term->blink_is_real = FALSE; + term->blink_is_real = false; term->curr_attr |= ATTR_BLINK; term_schedule_tblink(term); break; @@ -3835,9 +3883,9 @@ static void term_out(Terminal *term) compatibility(SCOANSI); if (term->no_remote_charset) break; term->sco_acs = 2; break; - case 22: /* disable bold */ + case 22: /* disable bold and dim */ compatibility2(OTHER, VT220); - term->curr_attr &= ~ATTR_BOLD; + term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM); break; case 24: /* disable underline */ compatibility2(OTHER, VT220); @@ -3860,6 +3908,7 @@ static void term_out(Terminal *term) 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; @@ -3891,6 +3942,7 @@ static void term_out(Terminal *term) 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; - case 38: /* xterm 256-colour mode */ + + /* + * 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: /* xterm 256-colour mode */ + 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; } } @@ -3939,10 +4025,10 @@ static void term_out(Terminal *term) } break; case 's': /* save cursor */ - save_cursor(term, TRUE); + save_cursor(term, true); break; case 'u': /* restore cursor */ - save_cursor(term, FALSE); + save_cursor(term, false); seen_disp_event(term); break; case 't': /* DECSLPP: set page size - ie window height */ @@ -3957,8 +4043,8 @@ static void term_out(Terminal *term) term->esc_args[0] >= 24)) { compatibility(VT340TEXT); if (!term->no_remote_resize) - request_resize(term->frontend, term->cols, - def(term->esc_args[0], 24)); + win_request_resize(term->win, term->cols, + def(term->esc_args[0], 24)); deselect(term); } else if (term->esc_nargs >= 1 && term->esc_args[0] >= 1 && @@ -3970,17 +4056,17 @@ static void term_out(Terminal *term) char buf[80]; const char *p; case 1: - set_iconic(term->frontend, FALSE); + win_set_minimised(term->win, false); break; case 2: - set_iconic(term->frontend, TRUE); + win_set_minimised(term->win, true); break; case 3: if (term->esc_nargs >= 3) { if (!term->no_remote_resize) - move_window(term->frontend, - def(term->esc_args[1], 0), - def(term->esc_args[2], 0)); + win_move(term->win, + def(term->esc_args[1], 0), + def(term->esc_args[2], 0)); } break; case 4: @@ -3991,56 +4077,60 @@ static void term_out(Terminal *term) break; case 5: /* move to top */ - set_zorder(term->frontend, TRUE); + win_set_zorder(term->win, true); break; case 6: /* move to bottom */ - set_zorder(term->frontend, FALSE); + win_set_zorder(term->win, false); break; case 7: - refresh_window(term->frontend); + win_refresh(term->win); break; case 8: if (term->esc_nargs >= 3) { if (!term->no_remote_resize) - request_resize(term->frontend, - def(term->esc_args[2], term->conf_width), - def(term->esc_args[1], term->conf_height)); + win_request_resize( + term->win, + def(term->esc_args[2], + term->conf_width), + def(term->esc_args[1], + term->conf_height)); } break; case 9: if (term->esc_nargs >= 2) - set_zoomed(term->frontend, - term->esc_args[1] ? - TRUE : FALSE); + win_set_maximised( + term->win, + term->esc_args[1] ? true : false); break; case 11: if (term->ldisc) ldisc_send(term->ldisc, - is_iconic(term->frontend) ? - "\033[2t" : "\033[1t", 4, 0); + win_is_minimised(term->win) ? + "\033[2t" : "\033[1t", 4, + false); break; case 13: if (term->ldisc) { - get_window_pos(term->frontend, &x, &y); + win_get_pos(term->win, &x, &y); len = sprintf(buf, "\033[3;%u;%ut", (unsigned)x, (unsigned)y); - ldisc_send(term->ldisc, buf, len, 0); + ldisc_send(term->ldisc, buf, len, false); } break; case 14: if (term->ldisc) { - get_window_pixels(term->frontend, &x, &y); + win_get_pixels(term->win, &x, &y); len = sprintf(buf, "\033[4;%d;%dt", y, x); - ldisc_send(term->ldisc, buf, len, 0); + 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, 0); + ldisc_send(term->ldisc, buf, len, false); } break; case 19: @@ -4064,26 +4154,32 @@ static void term_out(Terminal *term) if (term->ldisc && term->remote_qtitle_action != TITLE_NONE) { if(term->remote_qtitle_action == TITLE_REAL) - p = get_window_title(term->frontend, TRUE); + p = win_get_title(term->win, true); else p = EMPTY_WINDOW_TITLE; len = strlen(p); - ldisc_send(term->ldisc, "\033]L", 3, 0); - ldisc_send(term->ldisc, p, len, 0); - ldisc_send(term->ldisc, "\033\\", 2, 0); + ldisc_send(term->ldisc, "\033]L", 3, + false); + if (len > 0) + 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 = get_window_title(term->frontend, FALSE); + p = win_get_title(term->win, false); else p = EMPTY_WINDOW_TITLE; len = strlen(p); - ldisc_send(term->ldisc, "\033]l", 3, 0); - ldisc_send(term->ldisc, p, len, 0); - ldisc_send(term->ldisc, "\033\\", 2, 0); + ldisc_send(term->ldisc, "\033]l", 3, + false); + if (len > 0) + ldisc_send(term->ldisc, p, len, false); + ldisc_send(term->ldisc, "\033\\", 2, + false); } break; } @@ -4093,16 +4189,16 @@ static void term_out(Terminal *term) 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; + 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; + -def(term->esc_args[0], 1), true); + term->wrapnext = false; seen_disp_event(term); break; case ANSI('|', '*'): /* DECSNLS */ @@ -4115,9 +4211,9 @@ static void term_out(Terminal *term) compatibility(VT420); if (term->esc_nargs == 1 && term->esc_args[0] > 0) { if (!term->no_remote_resize) - request_resize(term->frontend, term->cols, - def(term->esc_args[0], - term->conf_height)); + win_request_resize(term->win, term->cols, + def(term->esc_args[0], + term->conf_height)); deselect(term); } break; @@ -4130,10 +4226,10 @@ static void term_out(Terminal *term) compatibility(VT340TEXT); if (term->esc_nargs <= 1) { if (!term->no_remote_resize) - request_resize(term->frontend, - def(term->esc_args[0], - term->conf_width), - term->rows); + win_request_resize( + term->win, + def(term->esc_args[0], term->conf_width), + term->rows); deselect(term); } break; @@ -4169,7 +4265,7 @@ static void term_out(Terminal *term) if (i == 0 || i == 1) { strcpy(buf, "\033[2;1;1;112;112;1;0x"); buf[2] += i; - ldisc_send(term->ldisc, buf, 20, 0); + ldisc_send(term->ldisc, buf, 20, false); } } break; @@ -4193,15 +4289,15 @@ static void term_out(Terminal *term) compatibility(SCOANSI); switch(term->esc_args[0]) { case 0: /* hide cursor */ - term->cursor_on = FALSE; + term->cursor_on = false; break; case 1: /* restore cursor */ - term->big_cursor = FALSE; - term->cursor_on = TRUE; + term->big_cursor = false; + term->cursor_on = true; break; case 2: /* block cursor */ - term->big_cursor = TRUE; - term->cursor_on = TRUE; + term->big_cursor = true; + term->cursor_on = true; break; } break; @@ -4215,14 +4311,14 @@ static void term_out(Terminal *term) compatibility(SCOANSI); if (term->esc_nargs >= 2) { if (term->esc_args[0] > term->esc_args[1]) - term->cursor_on = FALSE; + term->cursor_on = false; else - term->cursor_on = TRUE; + term->cursor_on = true; } break; case ANSI('D', '='): compatibility(SCOANSI); - term->blink_is_real = FALSE; + term->blink_is_real = false; term_schedule_tblink(term); if (term->esc_args[0]>=1) term->curr_attr |= ATTR_BLINK; @@ -4243,6 +4339,7 @@ static void term_out(Terminal *term) 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); @@ -4257,6 +4354,7 @@ static void term_out(Terminal *term) 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); @@ -4327,7 +4425,7 @@ static void term_out(Terminal *term) for (i = 1; i < term->esc_nargs; i++) { if (i != 1) strcat(term->id_string, ";"); - sprintf(lbuf, "%d", term->esc_args[i]); + sprintf(lbuf, "%u", term->esc_args[i]); strcat(term->id_string, lbuf); } strcat(term->id_string, "c"); @@ -4339,9 +4437,9 @@ static void term_out(Terminal *term) if (!has_compat(VT420) && has_compat(VT100)) { if (!term->no_remote_resize) { if (term->reset_132) - request_resize(132, 24); + win_request_resize(term->win, 132, 24); else - request_resize(80, 24); + win_request_resize(term->win, 80, 24); } } #endif @@ -4349,20 +4447,20 @@ static void term_out(Terminal *term) } break; case SEEN_OSC: - term->osc_w = FALSE; + term->osc_w = false; switch (c) { case 'P': /* Linux palette sequence */ term->termstate = SEEN_OSC_P; term->osc_strlen = 0; break; case 'R': /* Linux palette reset */ - palette_reset(term->frontend); + win_palette_reset(term->win); term_invalidate(term); term->termstate = TOPLEVEL; break; case 'W': /* word-set */ term->termstate = SEEN_OSC_W; - term->osc_w = TRUE; + term->osc_w = true; break; case '0': case '1': @@ -4374,25 +4472,40 @@ static void term_out(Terminal *term) 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'; + 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[0] = UINT_MAX; + term->esc_args[term->esc_nargs-1] = UINT_MAX; break; - case 'L': - /* - * Grotty hack to support xterm and DECterm title - * sequences concurrently. - */ - if (term->esc_args[0] == 2) { - term->esc_args[0] = 1; - break; - } - /* else fall through */ - default: - term->termstate = OSC_STRING; - term->osc_strlen = 0; + 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: @@ -4439,10 +4552,11 @@ static void term_out(Terminal *term) } term->osc_string[term->osc_strlen++] = val; if (term->osc_strlen >= 7) { - palette_set(term->frontend, term->osc_string[0], - term->osc_string[1] * 16 + term->osc_string[2], - term->osc_string[3] * 16 + term->osc_string[4], - term->osc_string[5] * 16 + term->osc_string[6]); + win_palette_set( + term->win, term->osc_string[0], + term->osc_string[1] * 16 + term->osc_string[2], + term->osc_string[3] * 16 + term->osc_string[4], + term->osc_string[5] * 16 + term->osc_string[6]); term_invalidate(term); term->termstate = TOPLEVEL; } @@ -4538,18 +4652,18 @@ static void term_out(Terminal *term) break; case 'I': if (term->curs.y == 0) - scroll(term, 0, term->rows - 1, -1, TRUE); + scroll(term, 0, term->rows - 1, -1, true); else if (term->curs.y > 0) term->curs.y--; - term->wrapnext = FALSE; + term->wrapnext = false; break; case 'J': - erase_lots(term, FALSE, FALSE, TRUE); + erase_lots(term, false, false, true); if (term->scroll_on_disp) term->disptop = 0; break; case 'K': - erase_lots(term, TRUE, FALSE, TRUE); + erase_lots(term, true, false, true); break; #if 0 case 'V': @@ -4567,20 +4681,20 @@ static void term_out(Terminal *term) break; case 'Z': if (term->ldisc) - ldisc_send(term->ldisc, "\033/Z", 3, 0); + ldisc_send(term->ldisc, "\033/Z", 3, false); break; case '=': - term->app_keypad_keys = TRUE; + term->app_keypad_keys = true; break; case '>': - term->app_keypad_keys = FALSE; + 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->vt52_mode = false; term->blink_is_real = term->blinktext; term_schedule_tblink(term); break; @@ -4600,19 +4714,19 @@ static void term_out(Terminal *term) case 'E': /* compatibility(ATARI) */ move(term, 0, 0, 0); - erase_lots(term, FALSE, FALSE, TRUE); + 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); + 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); + scroll(term, term->curs.y, term->marg_b, 1, true); break; case 'b': /* compatibility(ATARI) */ @@ -4624,29 +4738,29 @@ static void term_out(Terminal *term) break; case 'd': /* compatibility(ATARI) */ - erase_lots(term, FALSE, TRUE, FALSE); + erase_lots(term, false, true, false); if (term->scroll_on_disp) term->disptop = 0; break; case 'e': /* compatibility(ATARI) */ - term->cursor_on = TRUE; + term->cursor_on = true; break; case 'f': /* compatibility(ATARI) */ - term->cursor_on = FALSE; + 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); + erase_lots(term, true, true, true); term->curs.x = 0; - term->wrapnext = FALSE; + term->wrapnext = false; break; case 'o': /* compatibility(ATARI) */ - erase_lots(term, TRUE, TRUE, FALSE); + erase_lots(term, true, true, false); break; case 'p': /* compatibility(ATARI) */ @@ -4658,17 +4772,19 @@ static void term_out(Terminal *term) break; case 'v': /* wrap Autowrap on - Wyse style */ /* compatibility(ATARI) */ - term->wrap = 1; + term->wrap = true; break; case 'w': /* Autowrap off */ /* compatibility(ATARI) */ - term->wrap = 0; + term->wrap = false; break; case 'R': /* compatibility(OTHER) */ - term->vt52_bold = FALSE; + 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': @@ -4681,12 +4797,12 @@ static void term_out(Terminal *term) break; case 'U': /* compatibility(VI50) */ - term->vt52_bold = TRUE; + term->vt52_bold = true; term->curr_attr |= ATTR_BOLD; break; case 'T': /* compatibility(VI50) */ - term->vt52_bold = FALSE; + term->vt52_bold = false; term->curr_attr &= ~ATTR_BOLD; break; #endif @@ -4731,33 +4847,46 @@ static void term_out(Terminal *term) 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 int term_bidi_cache_hit(Terminal *term, int line, - termchar *lbefore, int width) +static bool term_bidi_cache_hit(Terminal *term, int line, + termchar *lbefore, int width) { int i; if (!term->pre_bidi_cache) - return FALSE; /* cache doesn't even exist yet! */ + return false; /* cache doesn't even exist yet! */ if (line >= term->bidi_cache_size) - return FALSE; /* cache doesn't have this many lines */ + return false; /* cache doesn't have this many lines */ if (!term->pre_bidi_cache[line].chars) - return FALSE; /* cache doesn't contain _this_ line */ + return false; /* cache doesn't contain _this_ line */ if (term->pre_bidi_cache[line].width != width) - return FALSE; /* line is wrong width */ + return false; /* line is wrong width */ for (i = 0; i < width; i++) if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i)) - return FALSE; /* line doesn't match cache */ + return false; /* line doesn't match cache */ - return TRUE; /* it didn't match. */ + return true; /* it didn't match. */ } static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore, @@ -4918,19 +5047,15 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata, } /* - * Given a context, update the window. Out of paranoia, we don't - * allow WM_PAINT responses to do scrolling optimisations. + * Given a context, update the window. */ -static void do_paint(Terminal *term, Context ctx, int may_optimise) +static void do_paint(Terminal *term) { int i, j, our_curs_y, our_curs_x; int rv, cursor; pos scrpos; wchar_t *ch; int chlen; -#ifdef OPTIMISE_SCROLL - struct scrollregion *sr; -#endif /* OPTIMISE_SCROLL */ termchar *newline; chlen = 1024; @@ -5010,29 +5135,19 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) } term->dispcursx = term->dispcursy = -1; -#ifdef OPTIMISE_SCROLL - /* Do scrolls */ - sr = term->scrollhead; - while (sr) { - struct scrollregion *next = sr->next; - do_scroll(ctx, sr->topline, sr->botline, sr->lines); - sfree(sr); - sr = next; - } - term->scrollhead = term->scrolltail = NULL; -#endif /* OPTIMISE_SCROLL */ - /* The normal screen data */ for (i = 0; i < term->rows; i++) { termline *ldata; termchar *lchars; - int dirty_line, dirty_run, selected; + bool dirty_line, dirty_run, selected; unsigned long attr = 0, cset = 0; int start = 0; int ccount = 0; - int last_run_dirty = 0; - int laststart, dirtyrect; + bool last_run_dirty = false; + int laststart; + bool dirtyrect; int *backward; + truecolour tc; scrpos.y = i + term->disptop; ldata = lineptr(scrpos.y); @@ -5072,6 +5187,12 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) 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]; @@ -5095,7 +5216,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) selected = (posPle(term->selstart, scrpos) && posPlt(scrpos, term->selend)); } else - selected = FALSE; + selected = false; tattr = (tattr ^ rv ^ (selected ? ATTR_REVERSE : 0)); @@ -5114,7 +5235,8 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) if (tchar != term->disptext[i]->chars[j].chr || tattr != (term->disptext[i]->chars[j].attr &~ (ATTR_NARROW | DATTR_MASK))) { - if ((tattr & ATTR_WIDE) == 0 && char_width(ctx, tchar) == 2) + 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; @@ -5129,6 +5251,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) /* 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; } @@ -5148,11 +5271,11 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) * with fonts that overflow their character cells. */ laststart = 0; - dirtyrect = FALSE; + dirtyrect = false; for (j = 0; j < term->cols; j++) { if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) { laststart = j; - dirtyrect = FALSE; + dirtyrect = false; } if (term->disptext[i]->chars[j].chr != newline[j].chr || @@ -5164,7 +5287,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) for (k = laststart; k < j; k++) term->disptext[i]->chars[k].attr |= ATTR_INVALID; - dirtyrect = TRUE; + dirtyrect = true; } } @@ -5179,25 +5302,29 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) 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; - int break_run, do_copy; + 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; + 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; + break_run = true; #endif /* @@ -5205,45 +5332,46 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) * same CSET, if that CSET is a magic one. */ if (CSET_OF(tchar) != cset) - break_run = TRUE; + 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_run = true; if (!term->ucsdata->dbcs_screenfont && !dirty_line) { if (term->disptext[i]->chars[j].chr == tchar && (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr) - break_run = TRUE; + break_run = true; else if (!dirty_run && ccount == 1) - break_run = TRUE; + break_run = true; } if (break_run) { if ((dirty_run || last_run_dirty) && ccount > 0) { - do_text(ctx, start, i, ch, ccount, attr, - ldata->lattr); + win_draw_text(term->win, start, i, ch, ccount, + attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) - do_cursor(ctx, start, i, ch, ccount, attr, - ldata->lattr); + win_draw_cursor(term->win, start, i, ch, ccount, attr, + ldata->lattr, 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; + do_copy = false; if (!termchars_equal_override(&term->disptext[i]->chars[j], d, tchar, tattr)) { - do_copy = TRUE; - dirty_run = TRUE; + do_copy = true; + dirty_run = true; } if (ccount+2 > chlen) { @@ -5301,6 +5429,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) 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; } @@ -5316,17 +5445,17 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) */ assert(!(i == our_curs_y && j == our_curs_x)); if (!termchars_equal(&term->disptext[i]->chars[j], d)) - dirty_run = TRUE; + dirty_run = true; copy_termchar(term->disptext[i], j, d); } } } if (dirty_run && ccount > 0) { - do_text(ctx, start, i, ch, ccount, attr, - ldata->lattr); + win_draw_text(term->win, start, i, ch, ccount, + attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) - do_cursor(ctx, start, i, ch, ccount, attr, - ldata->lattr); + win_draw_cursor(term->win, start, i, ch, ccount, + attr, ldata->lattr, tc); } unlineptr(ldata); @@ -5353,8 +5482,8 @@ void term_invalidate(Terminal *term) /* * Paint the window in response to a WM_PAINT message. */ -void term_paint(Terminal *term, Context ctx, - int left, int top, int right, int bottom, int immediately) +void term_paint(Terminal *term, + int left, int top, int right, int bottom, bool immediately) { int i, j; if (left < 0) left = 0; @@ -5372,7 +5501,7 @@ void term_paint(Terminal *term, Context ctx, } if (immediately) { - do_paint (term, ctx, FALSE); + do_paint(term); } else { term_schedule_update(term); } @@ -5388,10 +5517,6 @@ void term_paint(Terminal *term, Context ctx, void term_scroll(Terminal *term, int rel, int where) { int sbtop = -sblines(term); -#ifdef OPTIMISE_SCROLL - int olddisptop = term->disptop; - int shift; -#endif /* OPTIMISE_SCROLL */ term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where; if (term->disptop < sbtop) @@ -5399,11 +5524,6 @@ void term_scroll(Terminal *term, int rel, int where) if (term->disptop > 0) term->disptop = 0; update_sbar(term); -#ifdef OPTIMISE_SCROLL - shift = (term->disptop - olddisptop); - if (shift < term->rows && shift > -term->rows) - scroll_display(term, 0, term->rows - 1, shift); -#endif /* OPTIMISE_SCROLL */ term_update(term); } @@ -5442,9 +5562,11 @@ typedef struct { 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) +static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc) { if (b->bufpos >= b->buflen) { b->buflen *= 2; @@ -5452,27 +5574,33 @@ static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr) b->textptr = b->textbuf + b->bufpos; b->attrbuf = sresize(b->attrbuf, b->buflen, int); b->attrptr = b->attrbuf + b->bufpos; + b->tcbuf = sresize(b->tcbuf, b->buflen, 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, int rect, int desel) +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.buflen = 5120; buf.bufpos = 0; buf.textptr = buf.textbuf = snewn(buf.buflen, wchar_t); buf.attrptr = buf.attrbuf = snewn(buf.buflen, int); + buf.tcptr = buf.tcbuf = snewn(buf.buflen, truecolour); old_top_x = top.x; /* needed for rect==1 */ while (poslt(top, bottom)) { - int nl = FALSE; + bool nl = false; termline *ldata = lineptr(top.y); pos nlpos; @@ -5497,7 +5625,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) poslt(top, nlpos)) decpos(nlpos); if (poslt(nlpos, bottom)) - nl = TRUE; + nl = true; } else if (ldata->lattr & LATTR_WRAPPED2) { /* Ignore the last char on the line in a WRAPPED2 line. */ decpos(nlpos); @@ -5533,6 +5661,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) 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: @@ -5593,7 +5722,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) #endif for (p = cbuf; *p; p++) - clip_addchar(&buf, *p, attr); + clip_addchar(&buf, *p, attr, tc); if (ldata->chars[x].cc_next) x += ldata->chars[x].cc_next; @@ -5605,7 +5734,7 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) if (nl) { int i; for (i = 0; i < sel_nl_sz; i++) - clip_addchar(&buf, sel_nl[i], 0); + clip_addchar(&buf, sel_nl[i], 0, term->basic_erase_char.truecolour); } top.y++; top.x = rect ? old_top_x : 0; @@ -5613,15 +5742,38 @@ static void clipme(Terminal *term, pos top, pos bottom, int rect, int desel) unlineptr(ldata); } #if SELECTION_NUL_TERMINATED - clip_addchar(&buf, 0, 0); + clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour); #endif - /* Finally, transfer all that to the clipboard. */ - write_clip(term->frontend, buf.textbuf, buf.attrbuf, buf.bufpos, desel); - sfree(buf.textbuf); - sfree(buf.attrbuf); + /* 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) +void term_copyall(Terminal *term, const int *clipboards, int n_clipboards) { pos top; pos bottom; @@ -5630,7 +5782,42 @@ void term_copyall(Terminal *term) top.x = 0; bottom.y = find_last_nonempty_line(term, screen); bottom.x = term->cols; - clipme(term, top, bottom, 0, TRUE); + 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; + } } /* @@ -5876,7 +6063,8 @@ static void term_paste_callback(void *vterm) break; } if (term->ldisc) - luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, 0); + luni_send(term->ldisc, term->paste_buffer + term->paste_pos, n, + false); term->paste_pos += n; if (term->paste_pos < term->paste_len) { @@ -5889,78 +6077,115 @@ static void term_paste_callback(void *vterm) term->paste_len = 0; } -void term_do_paste(Terminal *term) +/* + * 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) { - wchar_t *data; - int len; + return alen >= blen && !wcsncmp(a, b, blen); +} - get_clip(term->frontend, &data, &len); - if (data && len > 0) { - wchar_t *p, *q; +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); - term_seen_key_event(term); /* pasted data counts */ + /* + * 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) { + memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } - if (term->paste_buffer) - sfree(term->paste_buffer); - term->paste_pos = term->paste_len = 0; - term->paste_buffer = snewn(len + 12, wchar_t); + p = data; + while (p < data + len) { + wchar_t wc = *p++; - if (term->bracketed_paste) { - memcpy(term->paste_buffer, L"\033[200~", 6 * sizeof(wchar_t)); - term->paste_len += 6; + 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'; } - p = q = data; - while (p < data + len) { - while (p < data + len && - !(p <= data + len - sel_nl_sz && - !memcmp(p, sel_nl, sizeof(sel_nl)))) - p++; - - { - int i; - for (i = 0; i < p - q; i++) { - term->paste_buffer[term->paste_len++] = q[i]; - } + if ((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 (p <= data + len - sel_nl_sz && - !memcmp(p, sel_nl, sizeof(sel_nl))) { - term->paste_buffer[term->paste_len++] = '\015'; - p += sel_nl_sz; + 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; } - q = p; } - if (term->bracketed_paste) { - memcpy(term->paste_buffer + term->paste_len, - L"\033[201~", 6 * sizeof(wchar_t)); - term->paste_len += 6; - } + 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) - luni_send(term->ldisc, term->paste_buffer, term->paste_len, 0); - if (term->paste_buffer) - sfree(term->paste_buffer); - term->paste_buffer = 0; - term->paste_pos = term->paste_len = 0; - } + if (term->bracketed_paste) { + memcpy(term->paste_buffer + term->paste_len, + L"\033[201~", 6 * sizeof(wchar_t)); + term->paste_len += 6; + } + + /* Assume a small paste will be OK in one go. */ + if (term->paste_len < 256) { + if (term->ldisc) + luni_send(term->ldisc, term->paste_buffer, term->paste_len, false); + if (term->paste_buffer) + sfree(term->paste_buffer); + term->paste_buffer = 0; + term->paste_pos = term->paste_len = 0; } - get_clip(term->frontend, NULL, NULL); queue_toplevel_callback(term_paste_callback, term); } void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, - Mouse_Action a, int x, int y, int shift, int ctrl, int alt) + Mouse_Action a, int x, int y, bool shift, bool ctrl, bool alt) { pos selpoint; termline *ldata; - int raw_mouse = (term->xterm_mouse && - !term->no_mouse_rep && - !(term->mouse_override && shift)); + bool raw_mouse = (term->xterm_mouse && + !term->no_mouse_rep && + !(term->mouse_override && shift)); int default_seltype; if (y < 0) { @@ -5974,7 +6199,18 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_scroll(term, 0, +1); } if (x < 0) { - if (y > 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 @@ -6010,7 +6246,8 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, */ if (raw_mouse && (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) { - int encstate = 0, r, c, wheel; + int encstate = 0, r, c; + bool wheel; char abuf[32]; int len = 0; @@ -6019,23 +6256,23 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, switch (braw) { case MBT_LEFT: encstate = 0x00; /* left button down */ - wheel = FALSE; + wheel = false; break; case MBT_MIDDLE: encstate = 0x01; - wheel = FALSE; + wheel = false; break; case MBT_RIGHT: encstate = 0x02; - wheel = FALSE; + wheel = false; break; case MBT_WHEEL_UP: encstate = 0x40; - wheel = TRUE; + wheel = true; break; case MBT_WHEEL_DOWN: encstate = 0x41; - wheel = TRUE; + wheel = true; break; default: return; @@ -6083,7 +6320,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32); } if (len > 0) - ldisc_send(term->ldisc, abuf, len, 0); + ldisc_send(term->ldisc, abuf, len, false); } return; } @@ -6202,7 +6439,9 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, * data to the clipboard. */ clipme(term, term->selstart, term->selend, - (term->seltype == RECTANGULAR), FALSE); + (term->seltype == RECTANGULAR), false, + term->mouse_select_clipboards, + term->n_mouse_select_clipboards); term->selstate = SELECTED; } else term->selstate = NO_SELECTION; @@ -6212,7 +6451,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, || a == MA_2CLK || a == MA_3CLK #endif )) { - request_paste(term->frontend); + term_request_paste(term, term->mouse_paste_clipboard); } /* @@ -6225,14 +6464,14 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked, term_update(term); } -int format_arrow_key(char *buf, Terminal *term, int xkey, int ctrl) +int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl) { char *p = buf; if (term->vt52_mode) p += sprintf((char *) p, "\x1B%c", xkey); else { - int app_flg = (term->app_cursor_keys && !term->no_applic_c); + bool app_flg = (term->app_cursor_keys && !term->no_applic_c); #if 0 /* * RDB: VT100 & VT102 manuals both state the app cursor @@ -6276,8 +6515,12 @@ static void deselect(Terminal *term) term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0; } -void term_deselect(Terminal *term) +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); @@ -6290,21 +6533,19 @@ void term_deselect(Terminal *term) term_out(term); } -int term_ldisc(Terminal *term, int option) +bool term_ldisc(Terminal *term, int option) { if (option == LD_ECHO) return term->term_echoing; if (option == LD_EDIT) return term->term_editing; - return FALSE; + return false; } -int term_data(Terminal *term, int is_stderr, const char *data, int len) +static void term_added_data(Terminal *term) { - bufchain_add(&term->inbuf, data, len); - if (!term->in_term_out) { - term->in_term_out = TRUE; + term->in_term_out = true; term_reset_cblink(term); /* * During drag-selects, we do not process terminal input, @@ -6313,8 +6554,14 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len) */ if (term->selstate != DRAGGING) term_out(term); - term->in_term_out = FALSE; + term->in_term_out = false; } +} + +int term_data(Terminal *term, bool is_stderr, const void *data, int len) +{ + bufchain_add(&term->inbuf, data, len); + term_added_data(term); /* * term_out() always completely empties inbuf. Therefore, @@ -6338,30 +6585,18 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len) return 0; } -/* - * Write untrusted data to the terminal. - * The only control character that should be honoured is \n (which - * will behave as a CRLF). - */ -int term_data_untrusted(Terminal *term, const char *data, int len) +static void term_data_untrusted(Terminal *term, const void *data, int len) { - int i; - /* FIXME: more sophisticated checking? */ - for (i = 0; i < len; i++) { - if (data[i] == '\n') - term_data(term, 1, "\r\n", 2); - else if (data[i] & 0x60) - term_data(term, 1, data + i, 1); - } - return 0; /* assumes that term_data() always returns 0 */ + sanitise_term_data(&term->inbuf, data, len); + term_added_data(term); } -void term_provide_logctx(Terminal *term, void *logctx) +void term_provide_logctx(Terminal *term, LogContext *logctx) { term->logctx = logctx; } -void term_set_focus(Terminal *term, int has_focus) +void term_set_focus(Terminal *term, bool has_focus) { term->has_focus = has_focus; term_schedule_cblink(term); @@ -6377,7 +6612,7 @@ char *term_get_ttymode(Terminal *term, const char *mode) if (strcmp(mode, "ERASE") == 0) { val = term->bksp_is_delete ? "^?" : "^H"; } else if (strcmp(mode, "IUTF8") == 0) { - val = frontend_is_utf8(term->frontend) ? "yes" : "no"; + val = win_is_utf8(term->win) ? "yes" : "no"; } /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */ /* FIXME: or ECHO and friends based on local echo state? */ @@ -6386,7 +6621,7 @@ char *term_get_ttymode(Terminal *term, const char *mode) struct term_userpass_state { size_t curr_prompt; - int done_prompt; /* printed out prompt yet? */ + bool done_prompt; /* printed out prompt yet? */ size_t pos; /* cursor position */ }; @@ -6394,8 +6629,7 @@ struct term_userpass_state { * Process some terminal data in the course of username/password * input. */ -int term_get_userpass_input(Terminal *term, prompts_t *p, - const unsigned char *in, int inlen) +int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) { struct term_userpass_state *s = (struct term_userpass_state *)p->data; if (!s) { @@ -6404,7 +6638,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, */ p->data = s = snew(struct term_userpass_state); s->curr_prompt = 0; - s->done_prompt = 0; + s->done_prompt = false; /* We only print the `name' caption if we have to... */ if (p->name_reqd && p->name) { size_t l = strlen(p->name); @@ -6432,38 +6666,38 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, while (s->curr_prompt < p->n_prompts) { prompt_t *pr = p->prompts[s->curr_prompt]; - int finished_prompt = 0; + bool finished_prompt = false; if (!s->done_prompt) { term_data_untrusted(term, pr->prompt, strlen(pr->prompt)); - s->done_prompt = 1; + s->done_prompt = true; s->pos = 0; } /* Breaking out here ensures that the prompt is printed even * if we're now waiting for user data. */ - if (!in || !inlen) break; + if (!input || !bufchain_size(input)) break; /* FIXME: should we be using local-line-editing code instead? */ - while (!finished_prompt && inlen) { - char c = *in++; - inlen--; + while (!finished_prompt && bufchain_size(input) > 0) { + char c; + bufchain_fetch_consume(input, &c, 1); switch (c) { case 10: case 13: - term_data(term, 0, "\r\n", 2); + term_data(term, false, "\r\n", 2); prompt_ensure_result_size(pr, s->pos + 1); pr->result[s->pos] = '\0'; /* go to next prompt, if any */ s->curr_prompt++; - s->done_prompt = 0; - finished_prompt = 1; /* break out */ + s->done_prompt = false; + finished_prompt = true; /* break out */ break; case 8: case 127: if (s->pos > 0) { if (pr->echo) - term_data(term, 0, "\b \b", 3); + term_data(term, false, "\b \b", 3); s->pos--; } break; @@ -6471,14 +6705,14 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, case 27: while (s->pos > 0) { if (pr->echo) - term_data(term, 0, "\b \b", 3); + term_data(term, false, "\b \b", 3); s->pos--; } break; case 3: case 4: /* Immediate abort. */ - term_data(term, 0, "\r\n", 2); + term_data(term, false, "\r\n", 2); sfree(s); p->data = NULL; return 0; /* user abort */ @@ -6493,7 +6727,7 @@ int term_get_userpass_input(Terminal *term, prompts_t *p, prompt_ensure_result_size(pr, s->pos + 1); pr->result[s->pos++] = c; if (pr->echo) - term_data(term, 0, &c, 1); + term_data(term, false, &c, 1); } break; } diff --git a/terminal.h b/terminal.h index 2ed9e6ef..e39090bb 100644 --- a/terminal.h +++ b/terminal.h @@ -20,15 +20,6 @@ typedef struct { int y, x; } pos; -#ifdef OPTIMISE_SCROLL -struct scrollregion { - struct scrollregion *next; - int topline; /* Top line of scroll region. */ - int botline; /* Bottom line of scroll region. */ - int lines; /* Number of lines to scroll by - +ve is forwards. */ -}; -#endif /* OPTIMISE_SCROLL */ - typedef struct termchar termchar; typedef struct termline termline; @@ -40,6 +31,7 @@ struct termchar { */ unsigned long chr; unsigned long attr; + truecolour truecolour; /* * The cc_next field is used to link multiple termchars @@ -60,7 +52,7 @@ struct termline { int cols; /* number of real columns on the line */ int size; /* number of allocated termchars * (cc-lists may make this > cols) */ - int temporary; /* TRUE if decompressed from scrollback */ + bool temporary; /* true if decompressed from scrollback */ int cc_free; /* offset to first cc in free list */ struct termchar *chars; }; @@ -91,46 +83,43 @@ struct terminal_tag { struct beeptime *beephead, *beeptail; int nbeeps; - int beep_overloaded; + bool beep_overloaded; long lastbeep; #define TTYPE termchar #define TSIZE (sizeof(TTYPE)) -#ifdef OPTIMISE_SCROLL - struct scrollregion *scrollhead, *scrolltail; -#endif /* OPTIMISE_SCROLL */ - int default_attr, curr_attr, save_attr; + 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 */ - int dec_om; /* DEC origin mode flag */ - int wrap, wrapnext; /* wrap flags */ - int insert; /* insert-mode flag */ + 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 */ - int save_utf, save_wnext; /* saved with cursor position */ - int rvideo; /* global reverse video flag */ + bool save_utf, save_wnext; /* saved with cursor position */ + bool rvideo; /* global reverse video flag */ unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */ - int cursor_on; /* cursor enabled flag */ - int reset_132; /* Flag ESC c resets to 80 cols */ - int use_bce; /* Use Background coloured erase */ - int cblinker; /* When blinking is the cursor on ? */ - int tblinker; /* When the blinking text is on */ - int blink_is_real; /* Actually blink blinking text */ - int term_echoing; /* Does terminal want local echo? */ - int term_editing; /* Does terminal want local edit? */ + 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 */ + bool term_echoing; /* Does terminal want local echo? */ + bool term_editing; /* Does terminal want local edit? */ int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */ - int vt52_bold; /* Force bold on non-bold colours */ - int utf; /* Are we in toggleable UTF-8 mode? */ + bool vt52_bold; /* Force bold on non-bold colours */ + bool utf; /* Are we in toggleable UTF-8 mode? */ int utf_state; /* Is there a pending UTF-8 character */ int utf_char; /* and what is it so far. */ int utf_size; /* The size of the UTF character. */ - int printing, only_printing; /* Are we doing ANSI printing? */ + 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; @@ -138,33 +127,38 @@ struct terminal_tag { /* 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; - int alt_save_utf, alt_save_wnext; + bool alt_save_utf; + bool alt_save_wnext; int alt_save_sco_acs; int rows, cols, savelines; - int has_focus; - int in_vbell; + bool has_focus; + bool in_vbell; long vbell_end; - int app_cursor_keys, app_keypad_keys, vt52_mode; - int repeat_off, cr_lf_return; - int seen_disp_event; - int big_cursor; + bool app_cursor_keys, app_keypad_keys, vt52_mode; + bool repeat_off, cr_lf_return; + bool seen_disp_event; + bool big_cursor; int xterm_mouse; /* send mouse messages to host */ - int xterm_extended_mouse; - int urxvt_extended_mouse; + bool xterm_extended_mouse; + bool urxvt_extended_mouse; int mouse_is_down; /* used while tracking mouse buttons */ - int bracketed_paste; + bool bracketed_paste; int cset_attr[2]; /* * Saved settings on the alternate screen. */ - int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins; - int alt_cset, alt_sco_acs, alt_utf; + int alt_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. */ @@ -176,12 +170,12 @@ struct terminal_tag { int esc_nargs; int esc_query; #define ANSI(x,y) ((x)+((y)<<8)) -#define ANSI_QUE(x) ANSI(x,TRUE) +#define ANSI_QUE(x) ANSI(x,1) #define OSC_STR_MAX 2048 int osc_strlen; char osc_string[OSC_STR_MAX + 1]; - int osc_w; + bool osc_w; char id_string[1024]; @@ -224,17 +218,18 @@ struct terminal_tag { wchar_t *paste_buffer; int paste_len, paste_pos; - void (*resize_fn)(void *, int, int); - void *resize_ctx; + Backend *backend; - void *ldisc; + Ldisc *ldisc; - void *frontend; + TermWin *win; - void *logctx; + 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 @@ -245,26 +240,26 @@ struct terminal_tag { Conf *conf; /* - * from_backend calls term_out, but it can also be called from - * the ldisc if the ldisc is called _within_ term_out. So we - * have to guard against re-entrancy - if from_backend is - * called recursively like this, it will simply add data to the - * end of the buffer term_out is in the process of working - * through. + * 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. */ - int in_term_out; + bool in_term_out; /* * We schedule a window update shortly after receiving terminal * data. This tracks whether one is currently pending. */ - int window_update_pending; + bool window_update_pending; long next_update; /* * Track pending blinks and tblinks. */ - int tblink_pending, cblink_pending; + bool tblink_pending, cblink_pending; long next_tblink, next_cblink; /* @@ -283,46 +278,56 @@ struct terminal_tag { * tree234 lookups which would be involved in fetching them from * the former every time. */ - int ansi_colour; + bool ansi_colour; char *answerback; int answerbacklen; - int arabicshaping; + bool arabicshaping; int beep; - int bellovl; + bool bellovl; int bellovl_n; int bellovl_s; int bellovl_t; - int bidi; - int bksp_is_delete; - int blink_cur; - int blinktext; - int cjk_ambig_wide; + bool bidi; + bool bksp_is_delete; + bool blink_cur; + bool blinktext; + bool cjk_ambig_wide; int conf_height; int conf_width; - int crhaslf; - int erase_to_scrollback; + bool crhaslf; + bool erase_to_scrollback; int funky_type; - int lfhascr; - int logflush; + bool lfhascr; + bool logflush; int logtype; - int mouse_override; - int nethack_keypad; - int no_alt_screen; - int no_applic_c; - int no_applic_k; - int no_dbackspace; - int no_mouse_rep; - int no_remote_charset; - int no_remote_resize; - int no_remote_wintitle; - int no_remote_clearscroll; - int rawcnp; - int rect_select; + 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; - int rxvt_homeend; - int scroll_on_disp; - int scroll_on_key; - int xterm_256_colour; + 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; }; #define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8) diff --git a/testback.c b/testback.c index 752ec4ca..b54c682e 100644 --- a/testback.c +++ b/testback.c @@ -32,140 +32,139 @@ #include "putty.h" -static const char *null_init(void *, void **, Conf *, char *, int, char **, - int, int); -static const char *loop_init(void *, void **, Conf *, char *, int, char **, - int, int); -static void null_free(void *); -static void loop_free(void *); -static void null_reconfig(void *, Conf *); -static int null_send(void *, char *, int); -static int loop_send(void *, char *, int); -static int null_sendbuffer(void *); -static void null_size(void *, int, int); -static void null_special(void *, Telnet_Special); -static const struct telnet_special *null_get_specials(void *handle); -static int null_connected(void *); -static int null_exitcode(void *); -static int null_sendok(void *); -static int null_ldisc(void *, int); -static void null_provide_ldisc(void *, void *); -static void null_provide_logctx(void *, void *); -static void null_unthrottle(void *, int); -static int null_cfg_info(void *); - -Backend null_backend = { +static const char *null_init(Seat *, Backend **, LogContext *, Conf *, + const char *, int, char **, int, int); +static const char *loop_init(Seat *, Backend **, LogContext *, Conf *, + const char *, int, char **, int, int); +static void null_free(Backend *); +static void loop_free(Backend *); +static void null_reconfig(Backend *, Conf *); +static int null_send(Backend *, const char *, int); +static int loop_send(Backend *, const char *, int); +static int 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 *, int); +static int null_cfg_info(Backend *); + +const struct BackendVtable null_backend = { null_init, null_free, null_reconfig, null_send, null_sendbuffer, null_size, null_special, null_get_specials, null_connected, null_exitcode, null_sendok, - null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle, + null_ldisc, null_provide_ldisc, null_unthrottle, null_cfg_info, NULL /* test_for_upstream */, "null", -1, 0 }; -Backend loop_backend = { +const struct BackendVtable loop_backend = { loop_init, loop_free, null_reconfig, loop_send, null_sendbuffer, null_size, null_special, null_get_specials, null_connected, null_exitcode, null_sendok, - null_ldisc, null_provide_ldisc, null_provide_logctx, null_unthrottle, + null_ldisc, null_provide_ldisc, null_unthrottle, null_cfg_info, NULL /* test_for_upstream */, "loop", -1, 0 }; struct loop_state { - Terminal *term; + Seat *seat; + Backend backend; }; -static const char *null_init(void *frontend_handle, void **backend_handle, - Conf *conf, char *host, int port, - char **realhost, int nodelay, int keepalive) { - +static const char *null_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, char **realhost, + int nodelay, int keepalive) { + *backend_handle = NULL; return NULL; } -static const char *loop_init(void *frontend_handle, void **backend_handle, - Conf *conf, char *host, int port, - char **realhost, int nodelay, int keepalive) { +static const char *loop_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, char **realhost, + int nodelay, int keepalive) { struct loop_state *st = snew(struct loop_state); - st->term = frontend_handle; - *backend_handle = st; + st->seat = seat; + *backend_handle = &st->backend; return NULL; } -static void null_free(void *handle) +static void null_free(Backend *be) { } -static void loop_free(void *handle) +static void loop_free(Backend *be) { + struct loop_state *st = container_of(be, struct loop_state, backend); - sfree(handle); + sfree(st); } -static void null_reconfig(void *handle, Conf *conf) { +static void null_reconfig(Backend *be, Conf *conf) { } -static int null_send(void *handle, char *buf, int len) { +static int null_send(Backend *be, const char *buf, int len) { return 0; } -static int loop_send(void *handle, char *buf, int len) { - struct loop_state *st = handle; +static int loop_send(Backend *be, const char *buf, int len) { + struct loop_state *st = container_of(be, struct loop_state, backend); - return from_backend(st->term, 0, buf, len); + return seat_output(st->seat, 0, buf, len); } -static int null_sendbuffer(void *handle) { +static int null_sendbuffer(Backend *be) { return 0; } -static void null_size(void *handle, int width, int height) { +static void null_size(Backend *be, int width, int height) { } -static void null_special(void *handle, Telnet_Special code) { +static void null_special(Backend *be, SessionSpecialCode code, int arg) { } -static const struct telnet_special *null_get_specials (void *handle) { +static const SessionSpecial *null_get_specials (Backend *be) { return NULL; } -static int null_connected(void *handle) { +static int null_connected(Backend *be) { return 0; } -static int null_exitcode(void *handle) { +static int null_exitcode(Backend *be) { return 0; } -static int null_sendok(void *handle) { +static int null_sendok(Backend *be) { return 1; } -static void null_unthrottle(void *handle, int backlog) { +static void null_unthrottle(Backend *be, int backlog) { } -static int null_ldisc(void *handle, int option) { +static int null_ldisc(Backend *be, int option) { return 0; } -static void null_provide_ldisc (void *handle, void *ldisc) { - -} - -static void null_provide_logctx(void *handle, void *logctx) { +static void null_provide_ldisc (Backend *be, Ldisc *ldisc) { } -static int null_cfg_info(void *handle) +static int null_cfg_info(Backend *be) { return 0; } diff --git a/testbn.c b/testbn.c index 05f62767..364f704a 100644 --- a/testbn.c +++ b/testbn.c @@ -7,6 +7,7 @@ * testdata/bignum.py. */ +#include #include #include #include @@ -31,10 +32,12 @@ int random_byte(void) return 0; } +void queue_idempotent_callback(IdempotentCallback *ic) { assert(0); } + #define fromxdigit(c) ( (c)>'9' ? ((c)&0xDF) - 'A' + 10 : (c) - '0' ) /* For Unix in particular, but harmless if this main() is reused elsewhere */ -const int buildinfo_gtk_relevant = FALSE; +const bool buildinfo_gtk_relevant = false; int main(int argc, char **argv) { @@ -198,7 +201,7 @@ int main(int argc, char **argv) freebn(answer); } else if (!strcmp(buf, "divmod")) { Bignum n, d, expect_q, expect_r, answer_q, answer_r; - int fail; + bool fail; if (ptrnum != 4) { printf("%d: divmod with %d parameters, expected 4\n", line, ptrnum); @@ -212,7 +215,7 @@ int main(int argc, char **argv) answer_q = bigdiv(n, d); answer_r = bigmod(n, d); - fail = FALSE; + fail = false; if (bignum_cmp(expect_q, answer_q) != 0) { char *as = bignum_decimal(n); char *bs = bignum_decimal(d); @@ -221,7 +224,7 @@ int main(int argc, char **argv) printf("%d: fail: %s / %s gave %s expected %s\n", line, as, bs, cs, ds); - fail = TRUE; + fail = true; sfree(as); sfree(bs); @@ -236,7 +239,7 @@ int main(int argc, char **argv) printf("%d: fail: %s mod %s gave %s expected %s\n", line, as, bs, cs, ds); - fail = TRUE; + fail = true; sfree(as); sfree(bs); diff --git a/testdata/colours.txt b/testdata/colours.txt index 33709d23..34dff8a5 100644 --- a/testdata/colours.txt +++ b/testdata/colours.txt @@ -4,19 +4,12 @@ Normal text and bold; reverse video and bold ANSI plus bold: 0 bold 1 bold 2 bold 3 bold 4 bold 5 bold 6 bold 7 bold xterm bright: fg0 bg0 fg1 bg1 fg2 bg2 fg3 bg3 fg4 bg4 fg5 bg5 fg6 bg6 fg7 bg7 xterm 256: greys                      reds   greens blues  yellow magent cyans  - 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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 - 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 - 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 - 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 - 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 - 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 - 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 - 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 - 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 - 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 - 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 - 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 +0001020304050607 08090a0b0c0d0e0f 1011121314151617 18191a1b1c1d1e1f +2021222324252627 28292a2b2c2d2e2f 3031323334353637 38393a3b3c3d3e3f +4041424344454647 48494a4b4c4d4e4f 5051525354555657 58595a5b5c5d5e5f +6061626364656667 68696a6b6c6d6e6f 7071727374757677 78797a7b7c7d7e7f +8081828384858687 88898a8b8c8d8e8f 9091929394959697 98999a9b9c9d9e9f +a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf +c0c1c2c3c4c5c6c7 c8c9cacbcccdcecf d0d1d2d3d4d5d6d7 d8d9dadbdcdddedf +e0e1e2e3e4e5e6e7 e8e9eaebecedeeef f0f1f2f3f4f5f6f7 f8f9fafbfcfdfeff +24-bit colour: SlateGrey OliveDrab goldenrod SaddleBrown DarkViolet (bg) diff --git a/timing.c b/timing.c index 696c1e1d..8036e438 100644 --- a/timing.c +++ b/timing.c @@ -164,7 +164,7 @@ unsigned long timing_last_clock(void) * Returns the time (in ticks) expected until the next timer after * that triggers. */ -int run_timers(unsigned long anow, unsigned long *next) +bool run_timers(unsigned long anow, unsigned long *next) { struct timer *first; @@ -176,7 +176,7 @@ int run_timers(unsigned long anow, unsigned long *next) first = (struct timer *)index234(timers, 0); if (!first) - return FALSE; /* no timers remaining */ + return false; /* no timers remaining */ if (find234(timer_contexts, first->ctx, NULL) == NULL) { /* @@ -200,7 +200,7 @@ int run_timers(unsigned long anow, unsigned long *next) * future. Return how long it has yet to go. */ *next = first->now; - return TRUE; + return true; } } } diff --git a/tree234.c b/tree234.c index f1c0c2ed..4cf9cb1e 100644 --- a/tree234.c +++ b/tree234.c @@ -29,6 +29,7 @@ #include #include +#include "defs.h" #include "tree234.h" #ifdef TEST @@ -107,6 +108,18 @@ static int countnode234(node234 * n) 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. */ @@ -514,99 +527,66 @@ void *index234(tree234 * t, int index) void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, int *index) { - node234 *n; - void *ret; - int c; - int idx, ecount, kcount, cmpret; + 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; - if (t->root == NULL) - return NULL; + /* Only LT / GT relations are permitted with a null query element. */ + assert(!(equal_permitted && !e)); if (cmp == NULL) cmp = t->cmp; - n = t->root; + 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); + } + /* - * Attempt to find the element itself. + * 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. */ - idx = 0; - ecount = -1; + if (relation == REL234_EQ) + return NULL; + /* - * Prepare a fake `cmp' result if e is 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). */ - cmpret = 0; - if (e == NULL) { - assert(relation == REL234_LT || relation == REL234_GT); - if (relation == REL234_LT) - cmpret = +1; /* e is a max: always greater */ - else if (relation == REL234_GT) - cmpret = -1; /* e is a min: always smaller */ - } - while (1) { - for (kcount = 0; kcount < 4; kcount++) { - if (kcount >= 3 || n->elems[kcount] == NULL || - (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) { - break; - } - if (n->kids[kcount]) - idx += n->counts[kcount]; - if (c == 0) { - ecount = kcount; - break; - } - idx++; - } - if (ecount >= 0) - break; - if (n->kids[kcount]) - n = n->kids[kcount]; - else - break; - } - - if (ecount >= 0) { - /* - * We have found the element we're looking for. It's - * n->elems[ecount], at tree index idx. If our search - * relation is EQ, LE or GE we can now go home. - */ - if (relation != REL234_LT && relation != REL234_GT) { - if (index) - *index = idx; - return n->elems[ecount]; - } - - /* - * Otherwise, we'll do an indexed lookup for the previous - * or next element. (It would be perfectly possible to - * implement these search types in a non-counted tree by - * going back up from where we are, but far more fiddly.) - */ - if (relation == REL234_LT) - idx--; - else - idx++; - } else { - /* - * We've found our way to the bottom of the tree and we - * know where we would insert this node if we wanted to: - * we'd put it in in place of the (empty) subtree - * n->kids[kcount], and it would have index idx - * - * But the actual element isn't there. So if our search - * relation is EQ, we're doomed. - */ - if (relation == REL234_EQ) - return NULL; - - /* - * Otherwise, we must do an index lookup for index idx-1 - * (if we're going left - LE or LT) or index idx (if we're - * going right - GE or GT). - */ - if (relation == REL234_LT || relation == REL234_LE) { - idx--; - } + if (relation == REL234_LT || relation == REL234_LE) { + ss.index--; } /* @@ -614,10 +594,10 @@ void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, * to do the rest. This will return NULL if the index is out of * bounds, which is exactly what we want. */ - ret = index234(t, idx); - if (ret && index) - *index = idx; - return ret; + toret = index234(t, ss.index); + if (toret && index) + *index = ss.index; + return toret; } void *find234(tree234 * t, void *e, cmpfn234 cmp) { @@ -632,6 +612,80 @@ 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; + direction = +1; + } else { + state->_hi = state->_last - 1; + direction = -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. @@ -681,7 +735,7 @@ static void *delpos234_internal(tree234 * t, int index) LOG((" moving to subtree %d\n", ki)); sub = n->kids[ki]; if (!sub->elems[1]) { - LOG((" subtree has only one element!\n", ki)); + LOG((" subtree has only one element!\n")); if (ki > 0 && n->kids[ki - 1]->elems[1]) { /* * Case 3a, left-handed variant. Child ki has @@ -1014,6 +1068,9 @@ void *del234(tree234 * t, void *e) */ #include +#include + +int n_errors = 0; /* * Error reporting function. @@ -1026,6 +1083,7 @@ void error(char *fmt, ...) vfprintf(stdout, fmt, ap); va_end(ap); printf("\n"); + n_errors++; } /* The array representation of the data. */ @@ -1414,6 +1472,73 @@ int findtest(void) } } +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]; @@ -1428,6 +1553,7 @@ int main(void) cmp = mycmp; verify(); + searchtest(); for (i = 0; i < 10000; i++) { j = randomnumber(&seed); j %= NSTR; @@ -1442,6 +1568,7 @@ int main(void) in[j] = 1; } findtest(); + searchtest(); } while (arraylen > 0) { @@ -1480,7 +1607,8 @@ int main(void) delpostest(j); } - return 0; + printf("%d errors found\n", n_errors); + return (n_errors != 0); } #endif diff --git a/tree234.h b/tree234.h index ba743087..55cbe360 100644 --- a/tree234.h +++ b/tree234.h @@ -132,6 +132,41 @@ void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index); void *findrelpos234(tree234 * t, void *e, cmpfn234 cmp, int relation, int *index); +/* + * A more general search type still. Use search234_start() to + * initialise one of these state structures; it will fill in + * state->element with an element of the tree, and state->index with + * the index of that element. If you don't like that element, call + * search234_step, with direction == -1 if you want an element earlier + * in the tree, or +1 if you want a later one. + * + * If either function returns state->element == NULL, then you've + * narrowed the search to a point between two adjacent elements, so + * there are no further elements left to return consistent with the + * constraints you've imposed. In this case, state->index tells you + * how many elements come before the point you narrowed down to. After + * this, you mustn't call search234_step again (unless the state + * structure is first reinitialised). + * + * The use of this search system is that you get both the candidate + * element _and_ its index at every stage, so you can use both of them + * to make your decision. Also, you can remember element pointers from + * earlier in the search. + * + * The fields beginning with underscores are private to the + * implementation, and only exposed so that clients can know how much + * space to allocate for the structure as a whole. Don't modify them. + * (Except that it's safe to copy the whole structure.) + */ +typedef struct search234_state { + void *element; + int index; + int _lo, _hi, _last, _base; + void *_node; +} search234_state; +void search234_start(search234_state *state, tree234 *t); +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. diff --git a/unix/gtkapp.c b/unix/gtkapp.c index 8b2a794f..e7f49df5 100644 --- a/unix/gtkapp.c +++ b/unix/gtkapp.c @@ -27,36 +27,27 @@ and you should get unix/PuTTY.app and unix/PTerm.app as output. TODO list for a sensible GTK3 PuTTY/pterm on OS X: -Menu items' keyboard shortcuts (Command-Q for Quit, Command-V for -Paste) do not currently work. It's intentional that if you turn on -'Command key acts as Meta' in the configuration then those shortcuts -should be superseded by the Meta-key functionality (e.g. Cmd-Q should -send ESC Q to the session), for the benefit of people whose non-Mac -keyboard reflexes expect the Meta key to be in that position; but if -you don't turn that option on, then these shortcuts should work as an -ordinary Mac user expects, and currently they don't. - -Windows don't close sensibly when their sessions terminate. This is -because until now I've relied on calling cleanup_exit() or -gtk_main_quit() in gtkwin.c to terminate the program, which is -conceptually wrong in this situation (we don't want to quit the whole -application when just one window closes) and also doesn't reliably -work anyway (GtkApplication doesn't seem to have a gtk_main invocation -in it at all, so those calls to gtk_main_quit produce a GTK assertion -failure message on standard error). Need to introduce a proper 'clean -up this struct gui_data' function (including finalising other stuff -dangling off it like the backend), call that, and delete just that one -window. (And then work out a replacement mechanism for having the -ordinary Unix-style gtkmain.c based programs terminate when their -session does.) connection_fatal() in particular should invoke this -mechanism, and terminate just the connection that had trouble. +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. - -There doesn't seem to be a resize handle on terminal windows. I don't -think this is a fundamental limitation of OS X GTK (their demo app has -one), so perhaps I need to do something to make sure it appears? +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, @@ -64,37 +55,6 @@ 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. -I want to arrange *some* way to paste efficiently using my Apple -wireless keyboard and trackpad. The trackpad doesn't provide a middle -button; I can't use the historic Shift-Ins shortcut because the -keyboard has no Ins key; I configure the Command key to be Meta, so -Command-V is off the table too. I can always use the menu, but I'd -prefer there to be _some_ easily reachable mouse or keyboard gesture. - -Revamping the clipboard handling in general is going to be needed, as -well. Not everybody will want the current auto-copy-on-select -behaviour inherited from ordinary Unix PuTTY. Should arrange to have a -mode in which you have to take an explicit Copy action, and then -arrange that the Edit menu includes one of those. - -Dialog boxes shouldn't be modal. I think this is a good policy change -in general, and the required infrastructure changes will benefit the -Windows front end too, but for a multi-session process it's even more -critical - you need to be able to type into one session window while -setting up the configuration for launching another. So everywhere we -currently run a sub-instance of gtk_main, or call any API function -that implicitly does that (like gtk_dialog_run), we should switch to -putting up the dialog box and going back to our ordinary main loop, -and whatever we were going to do after the dialog closed we should -remember to do it when that happens later on. Also then we can remove -the post_main() horror from gtkcomm.c. - -The application menu bar is very minimal at the moment. Should include -all the usual stuff from the Ctrl-right-click menu - saved sessions, -mid-session special commands, Duplicate Session, Change Settings, -Event Log, clear scrollback, reset terminal, about box, anything else -I can think of. - 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. @@ -124,10 +84,11 @@ I suppose I'll have to look into OS X code signing. #define MAY_REFER_TO_GTK_IN_HEADERS #include "putty.h" +#include "gtkmisc.h" char *x_get_default(const char *key) { return NULL; } -const int buildinfo_gtk_relevant = TRUE; +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, @@ -135,13 +96,15 @@ const int buildinfo_gtk_relevant = TRUE; * in the source than it is to remove it in the makefile edifice. */ int main(int argc, char **argv) { - fprintf(stderr, "launcher does nothing on non-OSX platforms\n"); + fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n"); return 1; } -GtkWidget *make_gtk_toplevel_window(void *frontend) { return NULL; } +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) @@ -162,25 +125,84 @@ static void startup(GApplication *app, gpointer user_data) 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)); } -static void paste_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - request_paste(user_data); -} +#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[] = { - { "paste", paste_cb }, +#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(void *frontend) +GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { GtkWidget *win = gtk_application_window_new(app); g_action_map_add_action_entries(G_ACTION_MAP(win), @@ -190,21 +212,27 @@ GtkWidget *make_gtk_toplevel_window(void *frontend) return win; } -extern int cfgbox(Conf *conf); - void launch_duplicate_session(Conf *conf) { - extern const int dup_check_launchable; assert(!dup_check_launchable || conf_launchable(conf)); - new_session_window(conf, NULL); + g_application_hold(G_APPLICATION(app)); + new_session_window(conf_copy(conf), NULL); } -void launch_new_session(void) +void session_window_closed(void) { - Conf *conf = conf_new(); - do_defaults(NULL, conf); - if (conf_launchable(conf) || cfgbox(conf)) { + 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)); } } @@ -212,16 +240,41 @@ void launch_saved_session(const char *str) { Conf *conf = conf_new(); do_defaults(str, conf); - if (conf_launchable(conf) || cfgbox(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) { @@ -242,8 +295,16 @@ static void quit_cb(GSimpleAction *action, 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 }, }; @@ -251,15 +312,11 @@ int main(int argc, char **argv) { int status; - { - /* Call the function in ux{putty,pterm}.c to do app-type - * specific setup */ - extern void setup(int); - setup(FALSE); /* FALSE means we are not a one-session process */ - } + /* 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) { - extern char *pty_osx_envrestore_prefix; pty_osx_envrestore_prefix = argv[--argc]; } diff --git a/unix/gtkask.c b/unix/gtkask.c index 8493807b..532b260d 100644 --- a/unix/gtkask.c +++ b/unix/gtkask.c @@ -14,6 +14,7 @@ #include #endif +#include "defs.h" #include "gtkfont.h" #include "gtkcompat.h" #include "gtkmisc.h" @@ -27,7 +28,8 @@ struct drawing_area_ctx { #ifndef DRAW_DEFAULT_CAIRO GdkColor *cols; #endif - int width, height, current; + int width, height; + enum { NOT_CURRENT, CURRENT, GREYED_OUT } state; }; struct askpass_ctx { @@ -39,15 +41,18 @@ struct askpass_ctx { #endif #ifndef DRAW_DEFAULT_CAIRO GdkColormap *colmap; - GdkColor cols[2]; + GdkColor cols[3]; #endif - char *passphrase; + 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 void visually_acknowledge_keypress(struct askpass_ctx *ctx) @@ -56,9 +61,9 @@ static void visually_acknowledge_keypress(struct askpass_ctx *ctx) new_active = rand() % (N_DRAWING_AREAS - 1); if (new_active >= ctx->active_area) new_active++; - ctx->drawingareas[ctx->active_area].current = 0; + ctx->drawingareas[ctx->active_area].state = NOT_CURRENT; gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area); - ctx->drawingareas[new_active].current = 1; + ctx->drawingareas[new_active].state = CURRENT; gtk_widget_queue_draw(ctx->drawingareas[new_active].area); ctx->active_area = new_active; } @@ -113,11 +118,12 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) event->type == GDK_KEY_PRESS) { smemclr(ctx->passphrase, ctx->passsize); ctx->passphrase = NULL; + ctx->error_message = dupstr("passphrase input cancelled"); gtk_main_quit(); } else { #if GTK_CHECK_VERSION(2,0,0) if (gtk_im_context_filter_keypress(ctx->imc, event)) - return TRUE; + return true; #endif if (event->type == GDK_KEY_PRESS) { @@ -153,7 +159,7 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) } } } - return TRUE; + return true; } #if GTK_CHECK_VERSION(2,0,0) @@ -172,21 +178,23 @@ static gint configure_area(GtkWidget *widget, GdkEventConfigure *event, ctx->width = event->width; ctx->height = event->height; gtk_widget_queue_draw(widget); - return TRUE; + return true; } #ifdef DRAW_DEFAULT_CAIRO static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx) { - cairo_set_source_rgb(cr, 1-ctx->current, 1-ctx->current, 1-ctx->current); + 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->current]); - gdk_draw_rectangle(win, gc, TRUE, 0, 0, ctx->width, ctx->height); + 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 @@ -196,7 +204,7 @@ 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; + return true; } #else static gint expose_area(GtkWidget *widget, GdkEventExpose *event, @@ -212,13 +220,14 @@ static gint expose_area(GtkWidget *widget, GdkEventExpose *event, askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx); #endif - return TRUE; + return true; } #endif -static int try_grab_keyboard(struct askpass_ctx *ctx) +static gboolean try_grab_keyboard(gpointer vctx) { - int ret; + struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; + int i, ret; #if GTK_CHECK_VERSION(3,20,0) /* @@ -226,16 +235,28 @@ static int try_grab_keyboard(struct askpass_ctx *ctx) * 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) - return FALSE; + goto fail; ctx->seat = seat; - ret = gdk_seat_grab(seat, gtk_widget_get_window(ctx->dialog), - GDK_SEAT_CAPABILITY_KEYBOARD, - TRUE, NULL, NULL, NULL, NULL); + 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. @@ -246,22 +267,22 @@ static int try_grab_keyboard(struct askpass_ctx *ctx) dm = gdk_display_get_device_manager (gtk_widget_get_display(ctx->dialog)); if (!dm) - return FALSE; + goto fail; pointer = gdk_device_manager_get_client_pointer(dm); if (!pointer) - return FALSE; + goto fail; keyboard = gdk_device_get_associated_device(pointer); if (!keyboard) - return FALSE; + goto fail; if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) - return FALSE; + goto fail; ctx->keyboard = keyboard; ret = gdk_device_grab(ctx->keyboard, gtk_widget_get_window(ctx->dialog), GDK_OWNERSHIP_NONE, - TRUE, + true, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, NULL, GDK_CURRENT_TIME); @@ -270,37 +291,83 @@ static int try_grab_keyboard(struct askpass_ctx *ctx) * It's much simpler in GTK 1 and 2! */ ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog), - FALSE, GDK_CURRENT_TIME); + false, GDK_CURRENT_TIME); #endif + if (ret != GDK_GRAB_SUCCESS) + goto fail; - return ret == GDK_GRAB_SUCCESS; -} + /* + * 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 = rand() % 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); + } -typedef int (try_grab_fn_t)(struct askpass_ctx *ctx); + return false; -static int repeatedly_try_grab(struct askpass_ctx *ctx, try_grab_fn_t fn) -{ + fail: /* - * Repeatedly try to grab some aspect of the X server. 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 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. */ - const useconds_t ms_limit = 5*1000000; /* try for 5 seconds */ - const useconds_t ms_step = 1000000/8; /* at 1/8 second intervals */ - useconds_t ms; - - for (ms = 0; ms < ms_limit; ms += ms_step) { - if (fn(ctx)) - return TRUE; - usleep(ms_step); + if (++ctx->nattempts >= 4) { + smemclr(ctx->passphrase, ctx->passsize); + ctx->passphrase = NULL; + ctx->error_message = dupstr("unable to grab keyboard after 5 seconds"); + gtk_main_quit(); + } else { + g_timeout_add(1000/8, try_grab_keyboard, ctx); } - return FALSE; + 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, @@ -323,12 +390,12 @@ static const char *gtk_askpass_setup(struct askpass_ctx *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); + 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 our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog), - ctx->promptlabel, TRUE, TRUE, 0); + ctx->promptlabel, true, true, 0); #if GTK_CHECK_VERSION(2,0,0) ctx->imc = gtk_im_multicontext_new(); #endif @@ -338,9 +405,10 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, 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]) + false, true, success); + if (!success[0] || !success[1]) return "unable to allocate colours"; } #endif @@ -352,14 +420,14 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, #ifndef DRAW_DEFAULT_CAIRO ctx->drawingareas[i].cols = ctx->cols; #endif - ctx->drawingareas[i].current = 0; + 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); + true, true, 5); g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), "configure_event", G_CALLBACK(configure_area), @@ -383,8 +451,7 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, gtk_widget_show(ctx->drawingareas[i].area); } - ctx->active_area = rand() % N_DRAWING_AREAS; - ctx->drawingareas[ctx->active_area].current = 1; + ctx->active_area = -1; /* * Arrange to receive key events. We don't really need to worry @@ -393,40 +460,23 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, * the prompt label at random, and we'll use gtk_grab_add to * ensure key events go to it. */ - gtk_widget_set_sensitive(ctx->promptlabel, TRUE); + gtk_widget_set_sensitive(ctx->dialog, true); #if GTK_CHECK_VERSION(2,0,0) - gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), TRUE); + gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true); #endif /* - * Actually show the window, and wait for it to be shown. + * Wait for the key-receiving widget to actually be created, in + * order to call gtk_grab_add on it. */ - gtk_widget_show_now(ctx->dialog); + g_signal_connect(G_OBJECT(ctx->dialog), "realize", + G_CALLBACK(realize), ctx); /* - * Now that the window is displayed, make it grab the input focus. + * Show the window. */ - gtk_grab_add(ctx->promptlabel); - if (!repeatedly_try_grab(ctx, try_grab_keyboard)) - return "unable to grab keyboard"; - - /* - * And 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->promptlabel), "key_press_event", - G_CALLBACK(key_event), ctx); - g_signal_connect(G_OBJECT(ctx->promptlabel), "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 + gtk_widget_show(ctx->dialog); return NULL; } @@ -434,9 +484,11 @@ static const char *gtk_askpass_setup(struct askpass_ctx *ctx, static void gtk_askpass_cleanup(struct askpass_ctx *ctx) { #if GTK_CHECK_VERSION(3,20,0) - gdk_seat_ungrab(ctx->seat); + if (ctx->seat) + gdk_seat_ungrab(ctx->seat); #elif GTK_CHECK_VERSION(3,0,0) - gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME); + if (ctx->keyboard) + gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME); #else gdk_keyboard_ungrab(GDK_CURRENT_TIME); #endif @@ -450,16 +502,16 @@ static void gtk_askpass_cleanup(struct askpass_ctx *ctx) gtk_widget_destroy(ctx->dialog); } -static int setup_gtk(const char *display) +static bool setup_gtk(const char *display) { - static int gtk_initialised = FALSE; + static bool gtk_initialised = false; int argc; char *real_argv[3]; char **argv = real_argv; - int ret; + bool ret; if (gtk_initialised) - return TRUE; + return true; argc = 0; argv[argc++] = dupstr("dummy"); @@ -473,33 +525,36 @@ static int setup_gtk(const char *display) return ret; } -const int buildinfo_gtk_relevant = TRUE; +const bool buildinfo_gtk_relevant = true; char *gtk_askpass_main(const char *display, const char *wintitle, - const char *prompt, int *success) + const char *prompt, bool *success) { struct askpass_ctx actx, *ctx = &actx; 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; + *success = false; return dupstr("unable to initialise GTK"); } if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) { - *success = FALSE; + *success = false; return dupprintf("%s", err); } gtk_main(); gtk_askpass_cleanup(ctx); if (ctx->passphrase) { - *success = TRUE; + *success = true; return ctx->passphrase; } else { - *success = FALSE; - return dupstr("passphrase input cancelled"); + *success = false; + return ctx->error_message; } } @@ -517,13 +572,14 @@ void modalfatalbox(const char *p, ...) int main(int argc, char **argv) { - int success, exitcode; + bool success; + int exitcode; char *ret; gtk_init(&argc, &argv); if (argc != 2) { - success = FALSE; + success = false; ret = dupprintf("usage: %s ", argv[0]); } else { srand(time(NULL)); diff --git a/unix/gtkcfg.c b/unix/gtkcfg.c index 4307176f..8b0ea31f 100644 --- a/unix/gtkcfg.c +++ b/unix/gtkcfg.c @@ -10,7 +10,7 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, void *dlg, +static void about_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_ACTION) { @@ -18,7 +18,7 @@ static void about_handler(union control *ctrl, void *dlg, } } -void gtk_setup_config_box(struct controlbox *b, int midsession, void *win) +void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) { struct controlset *s, *s2; union control *c; diff --git a/unix/gtkcols.c b/unix/gtkcols.c index e8223a72..4b963f6e 100644 --- a/unix/gtkcols.c +++ b/unix/gtkcols.c @@ -3,11 +3,29 @@ */ #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) @@ -18,9 +36,8 @@ static void columns_base_add(GtkContainer *container, GtkWidget *widget); static void columns_remove(GtkContainer *container, GtkWidget *widget); static void columns_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data); -#if !GTK_CHECK_VERSION(2,0,0) -static gint columns_focus(GtkContainer *container, GtkDirectionType dir); -#endif +static 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, @@ -88,19 +105,17 @@ GType columns_get_type(void) } #endif -#if !GTK_CHECK_VERSION(2,0,0) -static gint (*columns_inherited_focus)(GtkContainer *container, +static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container, GtkDirectionType direction); -#endif static void columns_class_init(ColumnsClass *klass) { #if !GTK_CHECK_VERSION(2,0,0) - /* GtkObjectClass *object_class = (GtkObjectClass *)klass; */ + GtkObjectClass *object_class = (GtkObjectClass *)klass; GtkWidgetClass *widget_class = (GtkWidgetClass *)klass; GtkContainerClass *container_class = (GtkContainerClass *)klass; #else - /* GObjectClass *object_class = G_OBJECT_CLASS(klass); */ + GObjectClass *object_class = G_OBJECT_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); #endif @@ -111,6 +126,7 @@ static void columns_class_init(ColumnsClass *klass) 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) @@ -133,22 +149,65 @@ static void columns_class_init(ColumnsClass *klass) container_class->remove = columns_remove; container_class->forall = columns_forall; container_class->child_type = columns_child_type; -#if !GTK_CHECK_VERSION(2,0,0) + /* Save the previous value of this method. */ if (!columns_inherited_focus) - columns_inherited_focus = container_class->focus; - container_class->focus = columns_focus; -#endif + 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); + 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 @@ -164,7 +223,7 @@ static void columns_map(GtkWidget *widget) g_return_if_fail(IS_COLUMNS(widget)); cols = COLUMNS(widget); - gtk_widget_set_mapped(GTK_WIDGET(cols), TRUE); + gtk_widget_set_mapped(GTK_WIDGET(cols), true); for (children = cols->children; children && (child = children->data); @@ -185,7 +244,7 @@ static void columns_unmap(GtkWidget *widget) g_return_if_fail(IS_COLUMNS(widget)); cols = COLUMNS(widget); - gtk_widget_set_mapped(GTK_WIDGET(cols), FALSE); + gtk_widget_set_mapped(GTK_WIDGET(cols), false); for (children = cols->children; children && (child = children->data); @@ -227,9 +286,9 @@ static gint columns_expose(GtkWidget *widget, GdkEventExpose *event) 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); + 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); @@ -246,7 +305,7 @@ static gint columns_expose(GtkWidget *widget, GdkEventExpose *event) gtk_widget_event(child->widget, (GdkEvent *)&child_event); } } - return FALSE; + return false; } #endif @@ -272,7 +331,7 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) ColumnsChild *child; GtkWidget *childw; GList *children; - gboolean was_visible; + bool was_visible; g_return_if_fail(container != NULL); g_return_if_fail(IS_COLUMNS(container)); @@ -312,9 +371,6 @@ static void columns_remove(GtkContainer *container, GtkWidget *widget) cols->taborder = g_list_remove_link(cols->taborder, children); g_list_free(children); -#if GTK_CHECK_VERSION(2,0,0) - gtk_container_set_focus_chain(container, cols->taborder); -#endif break; } } @@ -383,7 +439,7 @@ void columns_set_cols(Columns *cols, gint ncols, const gint *percentages) childdata->widget = NULL; childdata->ncols = ncols; childdata->percentages = g_new(gint, ncols); - childdata->force_left = FALSE; + childdata->force_left = false; for (i = 0; i < ncols; i++) childdata->percentages[i] = percentages[i]; @@ -404,18 +460,15 @@ void columns_add(Columns *cols, GtkWidget *child, childdata->widget = child; childdata->colstart = colstart; childdata->colspan = colspan; - childdata->force_left = FALSE; + 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_CHECK_VERSION(2,0,0) - gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); -#endif - if (gtk_widget_get_realized(GTK_WIDGET(cols))) gtk_widget_realize(child); @@ -454,7 +507,7 @@ void columns_force_left_align(Columns *cols, GtkWidget *widget) child = columns_find_child(cols, widget); g_return_if_fail(child != NULL); - child->force_left = TRUE; + child->force_left = true; if (gtk_widget_get_visible(widget)) gtk_widget_queue_resize(GTK_WIDGET(cols)); } @@ -497,38 +550,34 @@ void columns_taborder_last(Columns *cols, GtkWidget *widget) cols->taborder = g_list_remove_link(cols->taborder, children); g_list_free(children); cols->taborder = g_list_append(cols->taborder, widget); -#if GTK_CHECK_VERSION(2,0,0) - gtk_container_set_focus_chain(GTK_CONTAINER(cols), cols->taborder); -#endif break; } } -#if !GTK_CHECK_VERSION(2,0,0) /* * Override GtkContainer's focus movement so the user can * explicitly specify the tab order. */ -static gint columns_focus(GtkContainer *container, GtkDirectionType dir) +static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir) { Columns *cols; GList *pos; GtkWidget *focuschild; - g_return_val_if_fail(container != NULL, FALSE); - g_return_val_if_fail(IS_COLUMNS(container), FALSE); + g_return_val_if_fail(super != NULL, false); + g_return_val_if_fail(IS_COLUMNS(super), false); - cols = COLUMNS(container); + cols = COLUMNS(super); - if (!GTK_WIDGET_DRAWABLE(cols) || - !GTK_WIDGET_IS_SENSITIVE(cols)) - return FALSE; + if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) || + !gtk_widget_is_sensitive(GTK_WIDGET(cols))) + return false; - if (!GTK_WIDGET_CAN_FOCUS(container) && + if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) && (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) { - focuschild = container->focus_child; - gtk_container_set_focus_child(container, NULL); + 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; @@ -541,20 +590,20 @@ static gint columns_focus(GtkContainer *container, GtkDirectionType dir) if (focuschild) { if (focuschild == child) { focuschild = NULL; /* now we can start looking in here */ - if (GTK_WIDGET_DRAWABLE(child) && + if (gtk_widget_is_drawable(child) && GTK_IS_CONTAINER(child) && - !GTK_WIDGET_HAS_FOCUS(child)) { - if (gtk_container_focus(GTK_CONTAINER(child), dir)) - return TRUE; + !gtk_widget_has_focus(child)) { + if (CHILD_FOCUS(child, dir)) + return true; } } - } else if (GTK_WIDGET_DRAWABLE(child)) { + } else if (gtk_widget_is_drawable(child)) { if (GTK_IS_CONTAINER(child)) { - if (gtk_container_focus(GTK_CONTAINER(child), dir)) - return TRUE; - } else if (GTK_WIDGET_CAN_FOCUS(child)) { + if (CHILD_FOCUS(child, dir)) + return true; + } else if (gtk_widget_get_can_focus(child)) { gtk_widget_grab_focus(child); - return TRUE; + return true; } } @@ -564,11 +613,10 @@ static gint columns_focus(GtkContainer *container, GtkDirectionType dir) pos = pos->prev; } - return FALSE; + return false; } else - return columns_inherited_focus(container, dir); + return columns_inherited_focus(super, dir); } -#endif /* * Underlying parts of the layout algorithm, to compute the Columns @@ -621,7 +669,7 @@ static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width) 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")); + ? "true" : "false")); else printf("widget %p: ", child->widget); { diff --git a/unix/gtkcols.h b/unix/gtkcols.h index b7410cb4..32653b8d 100644 --- a/unix/gtkcols.h +++ b/unix/gtkcols.h @@ -41,7 +41,7 @@ struct ColumnsChild_tag { /* If `widget' is non-NULL, this entry represents an actual widget. */ GtkWidget *widget; gint colstart, colspan; - gboolean force_left; /* for recalcitrant GtkLabels */ + bool force_left; /* for recalcitrant GtkLabels */ ColumnsChild *same_height_as; /* Otherwise, this entry represents a change in the column setup. */ gint ncols; diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c index 28884654..6100990e 100644 --- a/unix/gtkcomm.c +++ b/unix/gtkcomm.c @@ -83,12 +83,12 @@ gboolean fd_input_func(GIOChannel *source, GIOCondition condition, */ if (condition & G_IO_PRI) select_result(sourcefd, 4); - if (condition & G_IO_IN) + if (condition & (G_IO_IN | G_IO_HUP)) select_result(sourcefd, 1); if (condition & G_IO_OUT) select_result(sourcefd, 2); - return TRUE; + return true; } #else void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) @@ -107,7 +107,7 @@ uxsel_id *uxsel_input_add(int fd, int rwx) { #if GTK_CHECK_VERSION(2,0,0) int flags = 0; - if (rwx & 1) flags |= G_IO_IN; + if (rwx & 1) flags |= G_IO_IN | G_IO_HUP; if (rwx & 2) flags |= G_IO_OUT; if (rwx & 4) flags |= G_IO_PRI; id->chan = g_io_channel_unix_new(fd); @@ -173,11 +173,11 @@ static gint timer_trigger(gpointer data) } /* - * Returning FALSE means 'don't call this timer again', which + * 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. + * in case, return false anyway. */ - return FALSE; + return false; } void timer_change_notify(unsigned long next) @@ -199,44 +199,13 @@ void timer_change_notify(unsigned long next) */ static guint toplevel_callback_idle_id; -static int idle_fn_scheduled; +static bool idle_fn_scheduled; static void notify_toplevel_callback(void *); -/* - * Replacement code for the gtk_quit_add() function, which GTK2 - in - * their unbounded wisdom - deprecated without providing any usable - * replacement, and which we were using to ensure that our idle - * function for toplevel callbacks was only run from the outermost - * gtk_main(). - * - * We must make sure that all our subsidiary calls to gtk_main() are - * followed by a call to post_main(), so that the idle function can be - * re-established when we end up back at the top level. - */ -void post_main(void) -{ - if (gtk_main_level() == 1) - notify_toplevel_callback(NULL); -} - static gint idle_toplevel_callback_func(gpointer data) { - if (gtk_main_level() > 1) { - /* - * We don't run callbacks if we're in the middle of a - * subsidiary gtk_main. So unschedule this idle function; it - * will be rescheduled by post_main() when we come back up a - * level, which is the earliest we might actually do - * something. - */ - if (idle_fn_scheduled) { /* double-check, just in case */ - g_source_remove(toplevel_callback_idle_id); - idle_fn_scheduled = FALSE; - } - } else { - run_toplevel_callbacks(); - } + run_toplevel_callbacks(); /* * If we've emptied our toplevel callback queue, unschedule @@ -246,18 +215,18 @@ static gint idle_toplevel_callback_func(gpointer data) */ if (!toplevel_callback_pending() && idle_fn_scheduled) { g_source_remove(toplevel_callback_idle_id); - idle_fn_scheduled = FALSE; + idle_fn_scheduled = false; } - return TRUE; + return true; } -static void notify_toplevel_callback(void *frontend) +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; + idle_fn_scheduled = true; } } diff --git a/unix/gtkcompat.h b/unix/gtkcompat.h index a218598d..b34eda10 100644 --- a/unix/gtkcompat.h +++ b/unix/gtkcompat.h @@ -33,6 +33,7 @@ #define g_signal_handler_disconnect gtk_signal_disconnect #define g_object_get_data gtk_object_get_data #define g_object_set_data gtk_object_set_data +#define g_object_set_data_full gtk_object_set_data_full #define g_object_ref_sink gtk_object_sink #define GDK_GRAB_SUCCESS GrabSuccess @@ -52,6 +53,7 @@ #define gtk_widget_get_parent(w) ((w)->parent) #define gtk_widget_set_allocation(w, a) ((w)->allocation = *(a)) #define gtk_container_get_border_width(c) ((c)->border_width) +#define gtk_container_get_focus_child(c) ((c)->focus_child) #define gtk_bin_get_child(b) ((b)->child) #define gtk_color_selection_dialog_get_color_selection(cs) ((cs)->colorsel) #define gtk_selection_data_get_target(sd) ((sd)->target) @@ -65,6 +67,8 @@ #define gtk_adjustment_set_page_increment(a, val) ((a)->page_increment = (val)) #define gtk_adjustment_set_step_increment(a, val) ((a)->step_increment = (val)) #define gtk_adjustment_get_value(a) ((a)->value) +#define gtk_selection_data_get_selection(a) ((a)->selection) +#define gdk_display_beep(disp) gdk_beep() #define gtk_widget_set_has_window(w, b) \ gtk1_widget_set_unset_flag(w, GTK_NO_WINDOW, !(b)) @@ -76,6 +80,10 @@ #define gtk_widget_get_mapped(w) GTK_WIDGET_MAPPED(w) #define gtk_widget_get_realized(w) GTK_WIDGET_REALIZED(w) #define gtk_widget_get_state(w) GTK_WIDGET_STATE(w) +#define gtk_widget_get_can_focus(w) GTK_WIDGET_CAN_FOCUS(w) +#define gtk_widget_is_drawable(w) GTK_WIDGET_DRAWABLE(w) +#define gtk_widget_is_sensitive(w) GTK_WIDGET_IS_SENSITIVE(w) +#define gtk_widget_has_focus(w) GTK_WIDGET_HAS_FOCUS(w) /* This is a bit of a bodge because it relies on us only calling this * macro as GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), so under @@ -83,6 +91,11 @@ * return the GDK default display. */ #define GDK_DISPLAY_XDISPLAY(x) GDK_DISPLAY() +#define GDK_KEY_C ('C') +#define GDK_KEY_V ('V') +#define GDK_KEY_c ('c') +#define GDK_KEY_v ('v') + #endif /* 2.0 */ #if !GTK_CHECK_VERSION(2,22,0) @@ -170,6 +183,10 @@ #endif /* 2.24 */ +#if !GTK_CHECK_VERSION(3,0,0) +#define GDK_IS_X11_WINDOW(window) (1) +#endif + #if GTK_CHECK_VERSION(3,0,0) #define STANDARD_OK_LABEL "_OK" #define STANDARD_OPEN_LABEL "_Open" @@ -183,7 +200,7 @@ #if GTK_CHECK_VERSION(3,0,0) #define gtk_hseparator_new() gtk_separator_new(GTK_ORIENTATION_HORIZONTAL) /* Fortunately, my hboxes and vboxes never actually set homogeneous to - * TRUE, so I can just wrap these deprecated constructors with a macro + * true, so I can just wrap these deprecated constructors with a macro * without also having to arrange a call to gtk_box_set_homogeneous. */ #define gtk_hbox_new(homogeneous, spacing) \ gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing) diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c index f1611948..84f9774f 100644 --- a/unix/gtkdlg.c +++ b/unix/gtkdlg.c @@ -24,6 +24,7 @@ #include #include #include +#include "x11misc.h" #endif #ifdef TESTMODE @@ -78,7 +79,10 @@ struct uctrl { struct dlgparam { tree234 *byctrl, *bywidget; void *data; - struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */ + 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; @@ -92,7 +96,10 @@ struct dlgparam { int nselparams; struct selparam *selparams; #endif + struct controlbox *ctrlbox; int retval; + post_dialog_fn_t after; + void *afterctx; }; #define FLAG_UPDATING_COMBO_LIST 1 #define FLAG_UPDATING_LISTBOX 2 @@ -142,8 +149,8 @@ static void colourchoose_response(GtkDialog *dialog, static void coloursel_ok(GtkButton *button, gpointer data); static void coloursel_cancel(GtkButton *button, gpointer data); #endif -static void window_destroy(GtkWidget *widget, gpointer data); -int get_listitemheight(GtkWidget *widget); +static void dlgparam_destroy(GtkWidget *widget, gpointer data); +static int get_listitemheight(GtkWidget *widget); static int uctrl_cmp_byctrl(void *av, void *bv) { @@ -193,7 +200,7 @@ 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->coloursel_result.ok = false; dp->window = dp->cancelbutton = NULL; #if !GTK_CHECK_VERSION(2,0,0) dp->treeitems = NULL; @@ -248,27 +255,24 @@ static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w) return ret; } -union control *dlg_last_focused(union control *ctrl, void *dlg) +union control *dlg_last_focused(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; if (dp->currfocus != ctrl) return dp->currfocus; else return dp->lastfocus; } -void dlg_radiobutton_set(union control *ctrl, void *dlg, int which) +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_RADIO); assert(uc->buttons != NULL); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), TRUE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true); } -int dlg_radiobutton_get(union control *ctrl, void *dlg) +int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); int i; @@ -280,25 +284,22 @@ int dlg_radiobutton_get(union control *ctrl, void *dlg) return 0; /* got to return something */ } -void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_CHECKBOX); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked); } -int dlg_checkbox_get(union control *ctrl, void *dlg) +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_CHECKBOX); return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); } -void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); GtkWidget *entry; char *tmpstring; @@ -335,9 +336,8 @@ void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) sfree(tmpstring); } -char *dlg_editbox_get(union control *ctrl, void *dlg) +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX); @@ -365,9 +365,8 @@ 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, void *dlg) +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -394,9 +393,8 @@ void dlg_listbox_clear(union control *ctrl, void *dlg) assert(!"We shouldn't get here"); } -void dlg_listbox_del(union control *ctrl, void *dlg, int index) +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -429,9 +427,9 @@ void dlg_listbox_del(union control *ctrl, void *dlg, int index) assert(!"We shouldn't get here"); } -void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) +void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { - dlg_listbox_addwithid(ctrl, dlg, text, 0); + dlg_listbox_addwithid(ctrl, dp, text, 0); } /* @@ -441,10 +439,9 @@ void dlg_listbox_add(union control *ctrl, void *dlg, 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, void *dlg, +void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, char const *text, int id) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -590,9 +587,8 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, dp->flags &= ~FLAG_UPDATING_COMBO_LIST; } -int dlg_listbox_getid(union control *ctrl, void *dlg, int index) +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -630,9 +626,8 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index) } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, void *dlg) +int dlg_listbox_index(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -715,9 +710,8 @@ int dlg_listbox_index(union control *ctrl, void *dlg) return -1; /* placate dataflow analysis */ } -int dlg_listbox_issel(union control *ctrl, void *dlg, int index) +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -757,7 +751,7 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) if (uc->treeview) { GtkTreeSelection *treesel; GtkTreePath *path; - int ret; + bool ret; assert(uc->treeview != NULL); treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)); @@ -770,12 +764,11 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) } #endif assert(!"We shouldn't get here"); - return -1; /* placate dataflow analysis */ + return false; /* placate dataflow analysis */ } -void dlg_listbox_select(union control *ctrl, void *dlg, int index) +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_EDITBOX || @@ -800,17 +793,17 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) items = gtk_container_children(GTK_CONTAINER(uc->list)); nitems = g_list_length(items); if (nitems > 0) { - int modified = FALSE; + 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; + modified = true; uc->adj->value = newtop; } else if (uc->adj->value < newbot - uc->adj->page_size) { - modified = TRUE; + modified = true; uc->adj->value = newbot - uc->adj->page_size; } if (modified) @@ -834,7 +827,7 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) 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); + path, NULL, false, 0.0, 0.0); gtk_tree_path_free(path); return; } @@ -842,9 +835,8 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) assert(!"We shouldn't get here"); } -void dlg_text_set(union control *ctrl, void *dlg, char const *text) +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_TEXT); @@ -853,9 +845,8 @@ void dlg_text_set(union control *ctrl, void *dlg, char const *text) gtk_label_set_text(GTK_LABEL(uc->text), text); } -void dlg_label_change(union control *ctrl, void *dlg, char const *text) +void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); switch (uc->ctrl->generic.type) { @@ -893,9 +884,8 @@ void dlg_label_change(union control *ctrl, void *dlg, char const *text) } } -void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* We must copy fn->path before passing it to gtk_entry_set_text. * See comment in dlg_editbox_set() for the reasons. */ @@ -906,18 +896,16 @@ void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) sfree(duppath); } -Filename *dlg_filesel_get(union control *ctrl, void *dlg) +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_FILESELECT); assert(uc->entry != NULL); return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); } -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); /* We must copy fs->name before passing it to gtk_entry_set_text. * See comment in dlg_editbox_set() for the reasons. */ @@ -928,9 +916,8 @@ void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) sfree(dupname); } -FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); assert(uc->ctrl->generic.type == CTRL_FONTSELECT); assert(uc->entry != NULL); @@ -942,7 +929,7 @@ FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, void *dlg) +void dlg_update_start(union control *ctrl, dlgparam *dp) { /* * Apparently we can't do this at all in GTK. GtkCList supports @@ -950,7 +937,7 @@ void dlg_update_start(union control *ctrl, void *dlg) */ } -void dlg_update_done(union control *ctrl, void *dlg) +void dlg_update_done(union control *ctrl, dlgparam *dp) { /* * Apparently we can't do this at all in GTK. GtkCList supports @@ -958,9 +945,8 @@ void dlg_update_done(union control *ctrl, void *dlg) */ } -void dlg_set_focus(union control *ctrl, void *dlg) +void dlg_set_focus(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc = dlg_find_byctrl(dp, ctrl); switch (ctrl->generic.type) { @@ -1035,18 +1021,11 @@ void dlg_set_focus(union control *ctrl, void *dlg) * indication to the user. dlg_beep() is a quick and easy generic * error; dlg_error() puts up a message-box or equivalent. */ -void dlg_beep(void *dlg) +void dlg_beep(dlgparam *dp) { - gdk_beep(); + gdk_display_beep(gdk_display_get_default()); } -#if !GTK_CHECK_VERSION(3,0,0) -static void errmsg_button_clicked(GtkButton *button, gpointer data) -{ - gtk_widget_destroy(GTK_WIDGET(data)); -} -#endif - static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) { #if !GTK_CHECK_VERSION(2,0,0) @@ -1078,52 +1057,16 @@ static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) #endif } -void dlg_error_msg(void *dlg, const char *msg) +void trivial_post_dialog_fn(void *vctx, int result) { - struct dlgparam *dp = (struct dlgparam *)dlg; - GtkWidget *window; - -#if GTK_CHECK_VERSION(3,0,0) - window = gtk_message_dialog_new(GTK_WINDOW(dp->window), - (GTK_DIALOG_MODAL | - GTK_DIALOG_DESTROY_WITH_PARENT), - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - "%s", msg); - gtk_dialog_run(GTK_DIALOG(window)); - gtk_widget_destroy(window); -#else - GtkWidget *hbox, *text, *ok; - - window = gtk_dialog_new(); - text = gtk_label_new(msg); - align_label_left(GTK_LABEL(text)); - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20); - gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))), - hbox, FALSE, FALSE, 20); - gtk_widget_show(text); - gtk_widget_show(hbox); - gtk_window_set_title(GTK_WINDOW(window), "Error"); - gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); - ok = gtk_button_new_with_label("OK"); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(window))), - ok, FALSE, FALSE, 0); - gtk_widget_show(ok); - gtk_widget_set_can_default(ok, TRUE); - gtk_window_set_default(GTK_WINDOW(window), ok); - g_signal_connect(G_OBJECT(ok), "clicked", - G_CALLBACK(errmsg_button_clicked), window); - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(window_destroy), NULL); - gtk_window_set_modal(GTK_WINDOW(window), TRUE); - gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(dp->window)); - set_transient_window_pos(dp->window, window); - gtk_widget_show(window); - gtk_main(); -#endif +} - post_main(); +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); } /* @@ -1131,16 +1074,14 @@ void dlg_error_msg(void *dlg, const char *msg) * processing is completed, and passes an integer value (typically * a success status). */ -void dlg_end(void *dlg, int value) +void dlg_end(dlgparam *dp, int value) { - struct dlgparam *dp = (struct dlgparam *)dlg; dp->retval = value; gtk_widget_destroy(dp->window); } -void dlg_refresh(union control *ctrl, void *dlg) +void dlg_refresh(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct uctrl *uc; if (ctrl) { @@ -1158,16 +1099,15 @@ void dlg_refresh(union control *ctrl, void *dlg) } } -void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) +void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) { - struct dlgparam *dp = (struct dlgparam *)dlg; 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); + gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false); #else GtkWidget *okbutton, *cancelbutton; GtkWidget *coloursel = @@ -1175,12 +1115,12 @@ void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) 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); + gtk_color_selection_set_has_opacity_control(cs, false); #endif - dp->coloursel_result.ok = FALSE; + dp->coloursel_result.ok = false; - gtk_window_set_modal(GTK_WINDOW(coloursel), TRUE); + gtk_window_set_modal(GTK_WINDOW(coloursel), true); #if GTK_CHECK_VERSION(3,0,0) { @@ -1244,17 +1184,16 @@ void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) gtk_widget_show(coloursel); } -int dlg_coloursel_results(union control *ctrl, void *dlg, - int *r, int *g, int *b) +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) { - struct dlgparam *dp = (struct dlgparam *)dlg; if (dp->coloursel_result.ok) { *r = dp->coloursel_result.r; *g = dp->coloursel_result.g; *b = dp->coloursel_result.b; - return 1; + return true; } else - return 0; + return false; } /* ---------------------------------------------------------------------- @@ -1278,7 +1217,7 @@ static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event, dp->currfocus = focus; } - return FALSE; + return false; } static void button_clicked(GtkButton *button, gpointer data) @@ -1315,7 +1254,7 @@ static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event, event, &return_val); return return_val; } - return FALSE; + return false; } static void editbox_changed(GtkEditable *ed, gpointer data) @@ -1333,7 +1272,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); - return FALSE; + return false; } #if !GTK_CHECK_VERSION(2,0,0) @@ -1343,7 +1282,7 @@ static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event, */ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, - gpointer data, int multiple) + gpointer data, bool multiple) { GtkAdjustment *adj = GTK_ADJUSTMENT(data); @@ -1430,22 +1369,22 @@ static gboolean listitem_key(GtkWidget *item, GdkEventKey *event, g_list_free(chead); } - return TRUE; + return true; } - return FALSE; + return false; } static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event, gpointer data) { - return listitem_key(item, event, data, FALSE); + 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); + return listitem_key(item, event, data, true); } static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, @@ -1459,7 +1398,7 @@ static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event, case GDK_2BUTTON_PRESS: uc->nclicks = 2; break; case GDK_3BUTTON_PRESS: uc->nclicks = 3; break; } - return FALSE; + return false; } static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, @@ -1469,9 +1408,9 @@ static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event, 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 true; } - return FALSE; + return false; } static void list_selchange(GtkList *list, gpointer data) @@ -1491,7 +1430,7 @@ static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction) if ((index < 0) || (index == 0 && direction < 0) || (index == g_list_length(children)-1 && direction > 0)) { - gdk_beep(); + gdk_display_beep(gdk_display_get_default()); return; } @@ -1561,7 +1500,7 @@ static gboolean draglist_valchange(gpointer data) sfree(ctx); - return FALSE; + return false; } static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path, @@ -1690,9 +1629,9 @@ static void colourchoose_response(GtkDialog *dialog, 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; + dp->coloursel_result.ok = true; } else { - dp->coloursel_result.ok = FALSE; + dp->coloursel_result.ok = false; } uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); @@ -1731,7 +1670,7 @@ static void coloursel_ok(GtkButton *button, gpointer data) dp->coloursel_result.b = (int) (255 * cvals[2]); } #endif - dp->coloursel_result.ok = TRUE; + dp->coloursel_result.ok = true; uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); } @@ -1740,7 +1679,7 @@ 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; + dp->coloursel_result.ok = false; uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); } @@ -1761,7 +1700,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, (const gchar *)NULL); - gtk_window_set_modal(GTK_WINDOW(filechoose), TRUE); + 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); @@ -1769,7 +1708,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) #else GtkWidget *filesel = gtk_file_selection_new(uc->ctrl->fileselect.title); - gtk_window_set_modal(GTK_WINDOW(filesel), TRUE); + gtk_window_set_modal(GTK_WINDOW(filesel), true); g_object_set_data (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", (gpointer)filesel); @@ -1799,7 +1738,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) gchar *spacings[] = { "c", "m", NULL }; GtkWidget *fontsel = gtk_font_selection_dialog_new("Select a font"); - gtk_window_set_modal(GTK_WINDOW(fontsel), TRUE); + 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, @@ -1815,9 +1754,12 @@ static void filefont_clicked(GtkButton *button, gpointer data) GdkFont *font = gdk_font_load(fontname); if (font) { XFontStruct *xfs = GDK_FONT_XFONT(font); - Display *disp = GDK_FONT_XDISPLAY(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); @@ -1853,7 +1795,7 @@ static void filefont_clicked(GtkButton *button, gpointer data) unifontsel *fontsel = unifontsel_new("Select a font"); - gtk_window_set_modal(fontsel->window, TRUE); + gtk_window_set_modal(fontsel->window, true); unifontsel_set_name(fontsel, fontname); g_object_set_data(G_OBJECT(fontsel->ok_button), @@ -1938,7 +1880,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, for (i = 0; i < s->ncontrols; i++) { union control *ctrl = s->ctrls[i]; struct uctrl *uc; - int left = FALSE; + bool left = false; GtkWidget *w = NULL; switch (ctrl->generic.type) { @@ -1980,7 +1922,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, case CTRL_BUTTON: w = gtk_button_new_with_label(ctrl->generic.label); if (win) { - gtk_widget_set_can_default(w, TRUE); + gtk_widget_set_can_default(w, true); if (ctrl->button.isdefault) gtk_window_set_default(win, w); if (ctrl->button.iscancel) @@ -2001,7 +1943,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, G_CALLBACK(widget_focus), dp); shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc); - left = TRUE; + left = true; break; case CTRL_RADIO: /* @@ -2072,7 +2014,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, * GTK 1 combo box. */ w = gtk_combo_new(); - gtk_combo_set_value_in_list(GTK_COMBO(w), FALSE, TRUE); + 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; @@ -2094,7 +2036,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, } else { w = gtk_entry_new(); if (ctrl->editbox.password) - gtk_entry_set_visibility(GTK_ENTRY(w), FALSE); + gtk_entry_set_visibility(GTK_ENTRY(w), false); uc->entry = w; signalobject = w; } @@ -2287,7 +2229,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, * 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_pack_start(GTK_CELL_LAYOUT(w), cr, true); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr, "text", 1, NULL); @@ -2398,7 +2340,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, (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); + 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 : @@ -2412,7 +2354,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, G_CALLBACK(listbox_selchange), dp); if (ctrl->listbox.draglist) { - gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), TRUE); + gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), true); g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted", G_CALLBACK(listbox_reorder), dp); } @@ -2436,7 +2378,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, if (!ctrl->listbox.hscroll) { g_object_set(G_OBJECT(cellrend), "ellipsize", PANGO_ELLIPSIZE_END, - "ellipsize-set", TRUE, + "ellipsize-set", true, (const char *)NULL); } column = gtk_tree_view_column_new_with_attributes @@ -2543,7 +2485,7 @@ GtkWidget *layout_ctrls(struct dlgparam *dp, struct Shortcuts *scs, 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); + gtk_label_set_line_wrap(GTK_LABEL(w), true); break; } @@ -2615,13 +2557,8 @@ static void treeitem_sel(GtkItem *item, gpointer data) } #endif -static void window_destroy(GtkWidget *widget, gpointer data) -{ - gtk_main_quit(); -} - #if !GTK_CHECK_VERSION(2,0,0) -static int tree_grab_focus(struct dlgparam *dp) +static bool tree_grab_focus(struct dlgparam *dp) { int i, f; @@ -2636,10 +2573,10 @@ static int tree_grab_focus(struct dlgparam *dp) } if (f >= 0) - return FALSE; + return false; else { gtk_widget_grab_focus(dp->currtreeitem); - return TRUE; + return true; } } @@ -2650,7 +2587,7 @@ gint tree_focus(GtkContainer *container, GtkDirectionType direction, g_signal_stop_emission_by_name(G_OBJECT(container), "focus"); /* - * If there's a focused treeitem, we return FALSE to cause the + * 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. */ @@ -2658,13 +2595,13 @@ gint tree_focus(GtkContainer *container, GtkDirectionType direction, } #endif -int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +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; + return true; } if ((event->state & GDK_MOD1_MASK) && @@ -2779,11 +2716,11 @@ int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) } } - return FALSE; + return false; } #if !GTK_CHECK_VERSION(2,0,0) -int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +gint tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) { struct dlgparam *dp = (struct dlgparam *)data; @@ -2808,10 +2745,10 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) */ { GtkWidget *w = dp->treeitems[i]; - int vis = TRUE; + bool vis = true; while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) { if (!GTK_WIDGET_VISIBLE(w)) { - vis = FALSE; + vis = false; break; } w = w->parent; @@ -2828,7 +2765,7 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle"); gtk_widget_grab_focus(dp->treeitems[j]); } - return TRUE; + return true; } /* @@ -2838,15 +2775,15 @@ int tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) 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; + 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 true; } - return FALSE; + return false; } #endif @@ -2899,7 +2836,7 @@ void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw, shortcut_highlight(labelw, chr); } -int get_listitemheight(GtkWidget *w) +static int get_listitemheight(GtkWidget *w) { #if !GTK_CHECK_VERSION(2,0,0) GtkWidget *listitem = gtk_list_item_new_with_label("foo"); @@ -2958,13 +2895,13 @@ void treeview_map_event(GtkWidget *tree, gpointer data) } #endif -int do_config_box(const char *title, Conf *conf, int midsession, - int protcfginfo) +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; - struct controlbox *ctrlbox; char *path; #if GTK_CHECK_VERSION(2,0,0) GtkTreeStore *treestore; @@ -2976,13 +2913,17 @@ int do_config_box(const char *title, Conf *conf, int midsession, GtkTreeItem *treeitemlevels[8]; GtkTree *treelevels[8]; #endif - struct dlgparam dp; + struct dlgparam *dp; struct Shortcuts scs; struct selparam *selparams = NULL; int nselparams = 0, selparamsize = 0; - dlg_init(&dp); + 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; @@ -2990,22 +2931,22 @@ int do_config_box(const char *title, Conf *conf, int midsession, window = our_dialog_new(); - ctrlbox = ctrl_new_box(); + dp->ctrlbox = ctrl_new_box(); protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(ctrlbox, midsession, protocol, protcfginfo); - unix_setup_config_box(ctrlbox, midsession, protocol); - gtk_setup_config_box(ctrlbox, midsession, window); + 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); + 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); + 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_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); @@ -3016,7 +2957,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, 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); + 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); @@ -3028,29 +2969,28 @@ int do_config_box(const char *title, Conf *conf, int midsession, 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); + 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); + 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); + 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_notebook_set_show_tabs(GTK_NOTEBOOK(panels), false); + gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), false); + gtk_box_pack_start(GTK_BOX(hbox), panels, true, true, 0); gtk_widget_show(panels); panelvbox = NULL; path = NULL; level = 0; - for (index = 0; index < ctrlbox->nctrlsets; index++) { - struct controlset *s = ctrlbox->ctrlsets[index]; + for (index = 0; index < dp->ctrlbox->nctrlsets; index++) { + struct controlset *s = dp->ctrlbox->ctrlsets[index]; GtkWidget *w; if (!*s->pathname) { - w = layout_ctrls(&dp, &scs, s, GTK_WINDOW(window)); + w = layout_ctrls(dp, &scs, s, GTK_WINDOW(window)); our_dialog_set_action_area(GTK_WINDOW(window), w); } else { @@ -3062,7 +3002,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, #else GtkWidget *treeitem; #endif - int first; + bool first; /* * We expect never to find an implicit path @@ -3086,7 +3026,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, first = (panelvbox == NULL); - panelvbox = gtk_vbox_new(FALSE, 4); + panelvbox = gtk_vbox_new(false, 4); gtk_widget_show(panelvbox); gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox, NULL); @@ -3104,7 +3044,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, selparams = sresize(selparams, selparamsize, struct selparam); } - selparams[nselparams].dp = &dp; + selparams[nselparams].dp = dp; selparams[nselparams].panels = GTK_NOTEBOOK(panels); selparams[nselparams].panel = panelvbox; selparams[nselparams].shortcuts = scs; /* structure copy */ @@ -3139,7 +3079,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, */ gtk_tree_view_expand_row(GTK_TREE_VIEW(tree), selparams[nselparams].treepath, - FALSE); + false); } else { selparams[nselparams].treepath = NULL; } @@ -3164,9 +3104,9 @@ int do_config_box(const char *title, Conf *conf, int midsession, treelevels[j] = NULL; g_signal_connect(G_OBJECT(treeitem), "key_press_event", - G_CALLBACK(tree_key_press), &dp); + G_CALLBACK(tree_key_press), dp); g_signal_connect(G_OBJECT(treeitem), "focus_in_event", - G_CALLBACK(widget_focus), &dp); + G_CALLBACK(widget_focus), dp); gtk_widget_show(treeitem); @@ -3179,8 +3119,8 @@ int do_config_box(const char *title, Conf *conf, int midsession, nselparams++; } - w = layout_ctrls(&dp, &selparams[nselparams-1].shortcuts, s, NULL); - gtk_box_pack_start(GTK_BOX(panelvbox), w, FALSE, FALSE, 0); + w = layout_ctrls(dp, &selparams[nselparams-1].shortcuts, s, NULL); + gtk_box_pack_start(GTK_BOX(panelvbox), w, false, false, 0); gtk_widget_show(w); } } @@ -3201,8 +3141,8 @@ int do_config_box(const char *title, Conf *conf, int midsession, * enough to have all branches expanded without further resizing. */ - dp.nselparams = nselparams; - dp.selparams = selparams; + dp->nselparams = nselparams; + dp->selparams = selparams; #if !GTK_CHECK_VERSION(3,0,0) { @@ -3211,7 +3151,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, */ GtkRequisition req; gtk_widget_size_request(tree, &req); - initial_treeview_collapse(&dp, tree); + initial_treeview_collapse(dp, tree); gtk_widget_set_size_request(tree, req.width, -1); } #else @@ -3220,7 +3160,7 @@ int do_config_box(const char *title, Conf *conf, int midsession, * mapped, because the size computation won't have been done yet. */ g_signal_connect(G_OBJECT(tree), "map", - G_CALLBACK(treeview_map_event), &dp); + G_CALLBACK(treeview_map_event), dp); #endif /* GTK 2 vs 3 */ #endif /* GTK 2+ vs 1 */ @@ -3228,36 +3168,28 @@ int do_config_box(const char *title, Conf *conf, int midsession, g_signal_connect(G_OBJECT(treeselection), "changed", G_CALLBACK(treeselection_changed), selparams); #else - dp.ntreeitems = nselparams; - dp.treeitems = snewn(dp.ntreeitems, GtkWidget *); + 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; + dp->treeitems[index] = selparams[index].treeitem; } #endif - dp.data = conf; - dlg_refresh(NULL, &dp); + dp->data = conf; + dlg_refresh(NULL, dp); - dp.shortcuts = &selparams[0].shortcuts; + dp->shortcuts = &selparams[0].shortcuts; #if !GTK_CHECK_VERSION(2,0,0) - dp.currtreeitem = dp.treeitems[0]; + dp->currtreeitem = dp->treeitems[0]; #endif - dp.lastfocus = NULL; - dp.retval = 0; - dp.window = window; + dp->lastfocus = NULL; + dp->retval = -1; + dp->window = window; - { - /* in gtkwin.c */ - extern void set_window_icon(GtkWidget *window, - const char *const *const *icon, - int n_icon); - extern const char *const *const cfg_icon[]; - extern const int n_cfg_icon; - set_window_icon(window, cfg_icon, n_cfg_icon); - } + 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), @@ -3274,9 +3206,9 @@ int do_config_box(const char *title, Conf *conf, int midsession, /* * Set focus into the first available control. */ - for (index = 0; index < ctrlbox->nctrlsets; index++) { - struct controlset *s = ctrlbox->ctrlsets[index]; - int done = 0; + for (index = 0; index < dp->ctrlbox->nctrlsets; index++) { + struct controlset *s = dp->ctrlbox->ctrlsets[index]; + bool done = false; int j; if (*s->pathname) { @@ -3284,9 +3216,9 @@ int do_config_box(const char *title, Conf *conf, int midsession, if (s->ctrls[j]->generic.type != CTRL_TABDELAY && s->ctrls[j]->generic.type != CTRL_COLUMNS && s->ctrls[j]->generic.type != CTRL_TEXT) { - dlg_set_focus(s->ctrls[j], &dp); - dp.lastfocus = s->ctrls[j]; - done = 1; + dlg_set_focus(s->ctrls[j], dp); + dp->lastfocus = s->ctrls[j]; + done = true; break; } } @@ -3295,87 +3227,105 @@ int do_config_box(const char *title, Conf *conf, int midsession, } g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(window_destroy), NULL); + G_CALLBACK(dlgparam_destroy), dp); g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), &dp); + G_CALLBACK(win_key_press), dp); - gtk_main(); - post_main(); - - dlg_cleanup(&dp); - sfree(selparams); - ctrl_free_box(ctrlbox); + return window; +} - return dp.retval; +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) { + int i; + for (i = 0; i < dp->nselparams; i++) + if (dp->selparams[i].treepath) + gtk_tree_path_free(dp->selparams[i].treepath); + sfree(dp->selparams); + } +#endif + sfree(dp); } -static void messagebox_handler(union control *ctrl, void *dlg, +static void messagebox_handler(union control *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) - dlg_end(dlg, ctrl->generic.context.i); + dlg_end(dp, ctrl->generic.context.i); } -int messagebox(GtkWidget *parentwin, const char *title, const char *msg, - int minwid, int selectable, ...) + +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), +}; +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), +}; + +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) { GtkWidget *window, *w0, *w1; - struct controlbox *ctrlbox; struct controlset *s0, *s1; union control *c, *textctrl; - struct dlgparam dp; + struct dlgparam *dp; struct Shortcuts scs; - int index, ncols, min_type; - va_list ap; + int i, index, ncols, min_type; - dlg_init(&dp); + 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; } - ctrlbox = ctrl_new_box(); + dp->ctrlbox = ctrl_new_box(); /* - * Preliminary pass over the va_list, to count up the number of - * buttons and find out what kinds there are. + * Count up the number of buttons and find out what kinds there + * are. */ ncols = 0; - va_start(ap, selectable); min_type = +1; - while (va_arg(ap, char *) != NULL) { - int type; - - (void) va_arg(ap, int); /* shortcut */ - type = va_arg(ap, int); /* normal/default/cancel */ - (void) va_arg(ap, int); /* end value */ - + for (i = 0; i < buttons->nbuttons; i++) { + const struct message_box_button *button = &buttons->buttons[i]; ncols++; - if (min_type > type) - min_type = type; + if (min_type > button->type) + min_type = button->type; + assert(button->value >= 0); /* <0 means no return value available */ } - va_end(ap); - s0 = ctrl_getset(ctrlbox, "", "", ""); + 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; - va_start(ap, selectable); index = 0; - while (1) { - char *title = va_arg(ap, char *); - int shortcut, type, value; - if (title == NULL) - break; - shortcut = va_arg(ap, int); - type = va_arg(ap, int); - value = va_arg(ap, int); - c = ctrl_pushbutton(s0, title, shortcut, HELPCTX(no_help), - messagebox_handler, I(value)); + 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 (type > 0) - c->button.isdefault = TRUE; + 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 @@ -3386,34 +3336,33 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, * 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 (type == min_type) - c->button.iscancel = TRUE; + if (button->type == min_type) + c->button.iscancel = true; } - va_end(ap); - s1 = ctrl_getset(ctrlbox, "x", "", ""); + 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, &scs, s0, GTK_WINDOW(window)); + w0 = layout_ctrls(dp, &scs, s0, GTK_WINDOW(window)); our_dialog_set_action_area(GTK_WINDOW(window), w0); gtk_widget_show(w0); - w1 = layout_ctrls(&dp, &scs, s1, GTK_WINDOW(window)); + w1 = layout_ctrls(dp, &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); + 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; + 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); + 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 @@ -3430,7 +3379,6 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, #endif } - gtk_window_set_modal(GTK_WINDOW(window), TRUE); if (parentwin) { set_transient_window_pos(parentwin, window); gtk_window_set_transient_for(GTK_WINDOW(window), @@ -3441,37 +3389,73 @@ int messagebox(GtkWidget *parentwin, const char *title, const char *msg, 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(window_destroy), NULL); + G_CALLBACK(dlgparam_destroy), dp); g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), &dp); - - gtk_main(); - post_main(); + G_CALLBACK(win_key_press), dp); - dlg_cleanup(&dp); - ctrl_free_box(ctrlbox); - - return dp.retval; + return window; } -int reallyclose(void *frontend) +struct verify_ssh_host_key_result_ctx { + char *host; + int port; + char *keytype; + char *keystr; + void (*callback)(void *callback_ctx, int result); + void *callback_ctx; + Seat *seat; +}; + +static void verify_ssh_host_key_result_callback(void *vctx, int result) { - char *title = dupcat(appname, " Exit Confirmation", NULL); - int ret = messagebox(GTK_WIDGET(get_window(frontend)), - title, "Are you sure you want to close this session?", - string_width("Most of the width of the above text"), - FALSE, - "Yes", 'y', +1, 1, - "No", 'n', -1, 0, - NULL); - sfree(title); - return ret; + struct verify_ssh_host_key_result_ctx *ctx = + (struct verify_ssh_host_key_result_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); + + sfree(ctx->host); + sfree(ctx->keytype); + sfree(ctx->keystr); + sfree(ctx); } -int verify_ssh_host_key(void *frontend, char *host, int port, - const char *keytype, char *keystr, char *fingerprint, - 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, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) { static const char absenttxt[] = "The server's host key is not cached. You have no guarantee " @@ -3499,8 +3483,19 @@ int verify_ssh_host_key(void *frontend, char *host, int port, "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_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; /* * Verify the key. @@ -3512,59 +3507,89 @@ int verify_ssh_host_key(void *frontend, char *host, int port, text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint); - ret = messagebox(GTK_WIDGET(get_window(frontend)), - "PuTTY Security Alert", text, - string_width(fingerprint), - TRUE, - "Accept", 'a', 0, 2, - "Connect Once", 'o', 0, 1, - "Cancel", 'c', -1, 0, - NULL); + result_ctx = snew(struct verify_ssh_host_key_result_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)); + msgbox = create_message_box( + mainwin, "PuTTY Security Alert", text, string_width(fingerprint), true, + &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx); + register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); sfree(text); - if (ret == 2) { - store_host_key(host, port, keytype, keystr); - return 1; /* continue with connection */ - } else if (ret == 1) - return 1; /* continue with connection */ - return 0; /* do not continue with connection */ + 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; + + 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. + */ + unregister_dialog(ctx->seat, ctx->dialog_slot); + sfree(ctx); } /* * Ask whether the selected algorithm is acceptable (since it was * below the configured 'warn' threshold). */ -int askalg(void *frontend, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) +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; - int ret; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; text = dupprintf(msg, algtype, algname); - ret = messagebox(GTK_WIDGET(get_window(frontend)), - "PuTTY Security Alert", text, - string_width("Reasonably long line of text as a width" - " template"), - FALSE, - "Yes", 'y', 0, 1, - "No", 'n', 0, 0, - NULL); + + 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); - if (ret) { - return 1; - } else { - return 0; - } + return -1; /* dialog still in progress */ } -int askhk(void *frontend, const char *algname, const char *betteralgs, - 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) { static const char msg[] = "The first host key type we have stored for this server\n" @@ -3573,25 +3598,30 @@ int askhk(void *frontend, const char *algname, const char *betteralgs, "above the threshold, which we do not have stored:\n" "%s\n" "Continue with connection?"; + char *text; - int ret; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; text = dupprintf(msg, algname, betteralgs); - ret = messagebox(GTK_WIDGET(get_window(frontend)), - "PuTTY Security Alert", text, - string_width("is ecdsa-nistp521, which is" - " below the configured warning threshold."), - FALSE, - "Yes", 'y', 0, 1, - "No", 'n', 0, 0, - NULL); + + 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); - if (ret) { - return 1; - } else { - return 0; - } + return -1; /* dialog still in progress */ } void old_keyfile_warning(void) @@ -3601,30 +3631,14 @@ void old_keyfile_warning(void) */ } -void fatal_message_box(void *window, const char *msg) -{ - messagebox(window, "PuTTY Fatal Error", msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - FALSE, "OK", 'o', 1, 1, NULL); -} - void nonfatal_message_box(void *window, const char *msg) { - messagebox(window, "PuTTY Error", msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - FALSE, "OK", 'o', 1, 1, NULL); -} - -void fatalbox(const char *p, ...) -{ - va_list ap; - char *msg; - va_start(ap, p); - msg = dupvprintf(p, ap); - va_end(ap); - fatal_message_box(NULL, msg); - sfree(msg); - cleanup_exit(1); + char *title = dupcat(appname, " Error", NULL); + 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, ...) @@ -3661,10 +3675,10 @@ static void licence_clicked(GtkButton *button, gpointer data) title = dupcat(appname, " Licence", NULL); assert(aboutbox != NULL); - messagebox(aboutbox, title, LICENCE_TEXT("\n\n"), - string_width("LONGISH LINE OF TEXT SO THE LICENCE" - " BOX ISN'T EXCESSIVELY TALL AND THIN"), - TRUE, "OK", 'o', 1, 1, 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); } @@ -3686,17 +3700,17 @@ void about_box(void *window) sfree(title); w = gtk_button_new_with_label("Close"); - gtk_widget_set_can_default(w, TRUE); + 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); + 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); + 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); @@ -3710,14 +3724,15 @@ void about_box(void *window) 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); + 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); + 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 messagebox(). + * 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); @@ -3728,29 +3743,34 @@ void about_box(void *window) G_CALLBACK(about_key_press), NULL); set_transient_window_pos(GTK_WIDGET(window), aboutbox); - gtk_window_set_transient_for(GTK_WINDOW(aboutbox), - GTK_WINDOW(window)); + 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; - int nevents, negsize; + char **events_initial; + char **events_circular; + int ninitial, ncircular, circular_first; char *seldata; int sellen; - int ignore_selchange; + bool ignore_selchange; }; static void eventlog_destroy(GtkWidget *widget, gpointer data) { - struct eventlog_stuff *es = (struct eventlog_stuff *)data; + eventlog_stuff *es = (eventlog_stuff *)data; es->window = NULL; sfree(es->seldata); @@ -3758,26 +3778,29 @@ 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, void *dlg, +static void eventlog_ok_handler(union control *ctrl, dlgparam *dp, void *data, int event) { if (event == EVENT_ACTION) - dlg_end(dlg, 0); + dlg_end(dp, 0); } -static void eventlog_list_handler(union control *ctrl, void *dlg, +static void eventlog_list_handler(union control *ctrl, dlgparam *dp, void *data, int event) { - struct eventlog_stuff *es = (struct eventlog_stuff *)data; + eventlog_stuff *es = (eventlog_stuff *)data; if (event == EVENT_REFRESH) { int i; - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - for (i = 0; i < es->nevents; i++) { - dlg_listbox_add(ctrl, dlg, es->events[i]); + dlg_update_start(ctrl, dp); + dlg_listbox_clear(ctrl, dp); + for (i = 0; i < es->ninitial; i++) { + dlg_listbox_add(ctrl, dp, es->events_initial[i]); } - dlg_update_done(ctrl, dlg); + 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; int selsize = 0; @@ -3797,16 +3820,31 @@ static void eventlog_list_handler(union control *ctrl, void *dlg, sfree(es->seldata); es->seldata = NULL; es->sellen = 0; - for (i = 0; i < es->nevents; i++) { - if (dlg_listbox_issel(ctrl, dlg, i)) { - int extralen = strlen(es->events[i]); + for (i = 0; i < es->ninitial; i++) { + if (dlg_listbox_issel(ctrl, dp, i)) { + int extralen = strlen(es->events_initial[i]); + + if (es->sellen + extralen + 2 > selsize) { + selsize = es->sellen + extralen + 512; + es->seldata = sresize(es->seldata, selsize, char); + } + + strcpy(es->seldata + es->sellen, es->events_initial[i]); + es->sellen += extralen; + es->seldata[es->sellen++] = '\n'; + } + } + 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; + int extralen = strlen(es->events_circular[j]); if (es->sellen + extralen + 2 > selsize) { selsize = es->sellen + extralen + 512; es->seldata = sresize(es->seldata, selsize, char); } - strcpy(es->seldata + es->sellen, es->events[i]); + strcpy(es->seldata + es->sellen, es->events_circular[j]); es->sellen += extralen; es->seldata[es->sellen++] = '\n'; } @@ -3814,8 +3852,6 @@ static void eventlog_list_handler(union control *ctrl, void *dlg, if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME)) { - extern GdkAtom compound_text_atom; - gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 1); gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY, @@ -3828,7 +3864,7 @@ static void eventlog_list_handler(union control *ctrl, void *dlg, void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata, guint info, guint time_stamp, gpointer data) { - struct eventlog_stuff *es = (struct eventlog_stuff *)data; + eventlog_stuff *es = (eventlog_stuff *)data; gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8, (unsigned char *)es->seldata, es->sellen); @@ -3837,14 +3873,14 @@ void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata, gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, gpointer data) { - struct eventlog_stuff *es = (struct eventlog_stuff *)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 = 1; + es->ignore_selchange = true; #if !GTK_CHECK_VERSION(2,0,0) assert(uc->list); gtk_list_unselect_all(GTK_LIST(uc->list)); @@ -3853,17 +3889,16 @@ gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, gtk_tree_selection_unselect_all (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview))); #endif - es->ignore_selchange = 0; + es->ignore_selchange = false; sfree(es->seldata); es->sellen = 0; es->seldata = NULL; - return TRUE; + return true; } -void showeventlog(void *estuff, void *parentwin) +void showeventlog(eventlog_stuff *es, void *parentwin) { - struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; GtkWidget *window, *w0, *w1; GtkWidget *parent = GTK_WIDGET(parentwin); struct controlset *s0, *s1; @@ -3889,7 +3924,7 @@ void showeventlog(void *estuff, void *parentwin) c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help), eventlog_ok_handler, P(NULL)); c->button.column = 1; - c->button.isdefault = TRUE; + c->button.isdefault = true; s1 = ctrl_getset(es->eventbox, "x", "", ""); es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help), @@ -3903,7 +3938,7 @@ void showeventlog(void *estuff, void *parentwin) c->listbox.percentages[2] = 65; es->window = window = our_dialog_new(); - title = dupcat(appname, " Event Log", NULL); + title = dupcat(appname, " Event Log", (const char *)NULL); gtk_window_set_title(GTK_WINDOW(window), title); sfree(title); w0 = layout_ctrls(&es->dp, &es->scs, s0, GTK_WINDOW(window)); @@ -3915,7 +3950,7 @@ void showeventlog(void *estuff, void *parentwin) ("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); + our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); gtk_widget_show(w1); es->dp.data = es; @@ -3944,64 +3979,111 @@ void showeventlog(void *estuff, void *parentwin) G_CALLBACK(eventlog_selection_clear), es); } -void *eventlogstuff_new(void) +eventlog_stuff *eventlogstuff_new(void) { - struct eventlog_stuff *es; - es = snew(struct eventlog_stuff); + eventlog_stuff *es = snew(eventlog_stuff); memset(es, 0, sizeof(*es)); return es; } -void logevent_dlg(void *estuff, const char *string) +void eventlogstuff_free(eventlog_stuff *es) { - struct eventlog_stuff *es = (struct eventlog_stuff *)estuff; + 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); + } + + sfree(es); +} +void logevent_dlg(eventlog_stuff *es, const char *string) +{ char timebuf[40]; struct tm tm; + char **location; + size_t i; - if (es->nevents >= es->negsize) { - es->negsize += 64; - es->events = sresize(es->events, es->negsize, char *); + 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); - es->events[es->nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); - strcpy(es->events[es->nevents], timebuf); - strcat(es->events[es->nevents], string); + sfree(*location); + *location = dupcat(timebuf, string, NULL); if (es->window) { - dlg_listbox_add(es->listctrl, &es->dp, es->events[es->nevents]); + 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(".."); } - es->nevents++; } -int askappend(void *frontend, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) +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; - int mbret; + struct simple_prompt_result_ctx *result_ctx; + GtkWidget *mainwin, *msgbox; message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); mbtitle = dupprintf("%s Log to File", appname); - mbret = messagebox(get_window(frontend), mbtitle, message, - string_width("LINE OF TEXT SUITABLE FOR THE" - " ASKAPPEND WIDTH"), - FALSE, - "Overwrite", 'o', 1, 2, - "Append", 'a', 0, 1, - "Disable", 'd', -1, 0, - NULL); + 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 mbret; + return -1; /* dialog still in progress */ } diff --git a/unix/gtkfont.c b/unix/gtkfont.c index bb4c709a..ad29034e 100644 --- a/unix/gtkfont.c +++ b/unix/gtkfont.c @@ -31,6 +31,7 @@ #include #include #include +#include "x11misc.h" #endif /* @@ -51,10 +52,8 @@ * polymorphic. * * Any instance of `unifont' used in the vtable functions will - * actually be the first element of a larger structure containing - * data specific to the subtype. This is permitted by the ISO C - * provision that one may safely cast between a pointer to a - * structure and a pointer to its first element. + * actually be an element of a larger structure containing data + * specific to the subtype. */ #define FONTFLAG_CLIENTSIDE 0x0001 @@ -68,28 +67,29 @@ typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname, const char *family, const char *charset, const char *style, const char *stylekey, int size, int flags, - const struct unifont_vtable *fontclass); + const struct UnifontVtable *fontclass); -struct unifont_vtable { +struct UnifontVtable { /* * `Methods' of the `class'. */ - unifont *(*create)(GtkWidget *widget, const char *name, int wide, int bold, - int shadowoffset, int shadowalways); - unifont *(*create_fallback)(GtkWidget *widget, int height, int wide, - int bold, int shadowoffset, int shadowalways); + 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); - int (*has_glyph)(unifont *font, wchar_t glyph); + 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, - int wide, int bold, int cellwidth); + bool wide, bool bold, int cellwidth); void (*draw_combining)(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, int len, - int wide, int bold, int cellwidth); + 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, int resolve_aliases); + int *flags, bool resolve_aliases); char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); char *(*size_increment)(unifont *font, int increment); @@ -107,22 +107,23 @@ struct unifont_vtable { * back end other than X). */ -static int x11font_has_glyph(unifont *font, wchar_t glyph); +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, - int wide, int bold, int cellwidth); + 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, int wide, int bold, int cellwidth); + int len, bool wide, bool bold, + int cellwidth); static unifont *x11font_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways); + 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, - int resolve_aliases); + bool resolve_aliases); static char *x11font_scale_fontname(GtkWidget *widget, const char *name, int size); static char *x11font_size_increment(unifont *font, int increment); @@ -148,7 +149,7 @@ typedef struct x11font_individual { * haven't tried yet from xfs==NULL because we tried and failed, * so that we don't keep trying and failing subsequently). */ - int allocated; + bool allocated; #ifdef DRAW_TEXT_CAIRO /* @@ -176,7 +177,11 @@ typedef struct x11font_individual { } x11font_individual; struct x11font { - struct unifont u; + /* + * 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. @@ -187,13 +192,13 @@ struct x11font { * values larger than a byte. That is, this flag tells us * whether we use XDrawString or XDrawString16, etc. */ - int sixteen_bit; + 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. */ - int variable; + bool variable; /* * real_charset is the charset used when translating text into the * font's internal encoding inside draw_text(). This need not be @@ -205,10 +210,13 @@ struct x11font { /* * Data passed in to unifont_create(). */ - int wide, bold, shadowoffset, shadowalways; + int shadowoffset; + bool wide, bold, shadowalways; + + unifont u; }; -static const struct unifont_vtable x11font_vtable = { +static const struct UnifontVtable x11font_vtable = { x11font_create, NULL, /* no fallback fonts in X11 */ x11font_destroy, @@ -252,7 +260,6 @@ struct xlfd_decomposed { static struct xlfd_decomposed *xlfd_decompose(const char *xlfd) { - void *mem; char *p, *components[14]; struct xlfd_decomposed *dec; int i; @@ -260,15 +267,14 @@ static struct xlfd_decomposed *xlfd_decompose(const char *xlfd) if (!xlfd) return NULL; - mem = smalloc(sizeof(struct xlfd_decomposed) + strlen(xlfd) + 1); - p = ((char *)mem) + sizeof(struct xlfd_decomposed); + dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1); + p = snew_plus_get_aux(dec); strcpy(p, xlfd); - dec = (struct xlfd_decomposed *)mem; for (i = 0; i < 14; i++) { if (*p != '-') { /* Malformed XLFD: not enough '-' */ - sfree(mem); + sfree(dec); return NULL; } *p++ = '\0'; @@ -277,7 +283,7 @@ static struct xlfd_decomposed *xlfd_decompose(const char *xlfd) } if (*p) { /* Malformed XLFD: too many '-' */ - sfree(mem); + sfree(dec); return NULL; } @@ -308,9 +314,9 @@ static char *xlfd_recompose(const struct xlfd_decomposed *dec) #undef ARG_INT } -static char *x11_guess_derived_font_name(XFontStruct *xfs, int bold, int wide) +static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs, + bool bold, bool wide) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); Atom fontprop = XInternAtom(disp, "FONT", False); unsigned long ret; if (XGetFontProperty(xfs, fontprop, &ret)) { @@ -340,7 +346,7 @@ static char *x11_guess_derived_font_name(XFontStruct *xfs, int bold, int wide) return NULL; } -static int x11_font_width(XFontStruct *xfs, int sixteen_bit) +static int x11_font_width(XFontStruct *xfs, bool sixteen_bit) { if (sixteen_bit) { XChar2b space; @@ -385,13 +391,13 @@ static const XCharStruct *x11_char_struct( */ if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2) - return FALSE; + 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 FALSE; + return NULL; index = ((byte2 - xfs->min_char_or_byte2) + ((byte1 - xfs->min_byte1) * (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1))); @@ -403,7 +409,7 @@ static const XCharStruct *x11_char_struct( return &xfs->per_char[index]; } -static int x11_font_has_glyph( +static bool x11_font_has_glyph( XFontStruct *xfs, unsigned char byte1, unsigned char byte2) { /* @@ -423,17 +429,21 @@ static int x11_font_has_glyph( } static unifont *x11font_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways) + bool wide, bool bold, + int shadowoffset, bool shadowalways) { struct x11font *xfont; XFontStruct *xfs; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + Display *disp; Atom charset_registry, charset_encoding, spacing; unsigned long registry_ret, encoding_ret, spacing_ret; - int pubcs, realcs, sixteen_bit, variable; + 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; @@ -442,8 +452,8 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False); pubcs = realcs = CS_NONE; - sixteen_bit = FALSE; - variable = TRUE; + sixteen_bit = false; + variable = true; if (XGetFontProperty(xfs, charset_registry, ®istry_ret) && XGetFontProperty(xfs, charset_encoding, &encoding_ret)) { @@ -461,7 +471,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, * into 16-bit Unicode. */ if (!strcasecmp(encoding, "iso10646-1")) { - sixteen_bit = TRUE; + sixteen_bit = true; pubcs = realcs = CS_UTF8; } @@ -490,7 +500,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, spc = XGetAtomName(disp, (Atom)spacing_ret); if (spc && strchr("CcMm", spc[0])) - variable = FALSE; + variable = false; } xfont = snew(struct x11font); @@ -500,7 +510,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, 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.want_fallback = true; #ifdef DRAW_TEXT_GDK xfont->u.preferred_drawtype = DRAWTYPE_GDK; #elif defined DRAW_TEXT_CAIRO @@ -508,6 +518,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, #else #error No drawtype available at all #endif + xfont->disp = disp; xfont->real_charset = realcs; xfont->sixteen_bit = sixteen_bit; xfont->variable = variable; @@ -518,7 +529,7 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, for (i = 0; i < lenof(xfont->fonts); i++) { xfont->fonts[i].xfs = NULL; - xfont->fonts[i].allocated = FALSE; + xfont->fonts[i].allocated = false; #ifdef DRAW_TEXT_CAIRO xfont->fonts[i].glyphcache = NULL; xfont->fonts[i].nglyphs = 0; @@ -527,15 +538,15 @@ static unifont *x11font_create(GtkWidget *widget, const char *name, #endif } xfont->fonts[0].xfs = xfs; - xfont->fonts[0].allocated = TRUE; + xfont->fonts[0].allocated = true; - return (unifont *)xfont; + return &xfont->u; } static void x11font_destroy(unifont *font) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); - struct x11font *xfont = (struct x11font *)font; + struct x11font *xfont = container_of(font, struct x11font, u); + Display *disp = xfont->disp; int i; for (i = 0; i < lenof(xfont->fonts); i++) { @@ -556,23 +567,23 @@ static void x11font_destroy(unifont *font) } #endif } - sfree(font); + sfree(xfont); } static void x11_alloc_subfont(struct x11font *xfont, int sfid) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + Display *disp = xfont->disp; char *derived_name = x11_guess_derived_font_name - (xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); + (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name); - xfont->fonts[sfid].allocated = TRUE; + xfont->fonts[sfid].allocated = true; sfree(derived_name); /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */ } -static int x11font_has_glyph(unifont *font, wchar_t glyph) +static bool x11font_has_glyph(unifont *font, wchar_t glyph) { - struct x11font *xfont = (struct x11font *)font; + struct x11font *xfont = container_of(font, struct x11font, u); if (xfont->sixteen_bit) { /* @@ -588,9 +599,9 @@ static int 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, NULL); + sbstring, 2, "", NULL); if (sblen == 0 || !sbstring[0]) - return FALSE; /* not even in the charset */ + return false; /* not even in the charset */ return x11_font_has_glyph(xfont->fonts[0].xfs, 0, (unsigned char)sbstring[0]); @@ -618,27 +629,25 @@ static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi, } #ifdef DRAW_TEXT_GDK -static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi) +static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi, + Display *disp) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid); } -static void x11font_gdk_draw_16(unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, +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) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); 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, int x, int y, +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) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); 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); @@ -646,10 +655,10 @@ static void x11font_gdk_draw_8(unifont_drawctx *ctx, #endif #ifdef DRAW_TEXT_CAIRO -static void x11font_cairo_setup(unifont_drawctx *ctx, x11font_individual *xfi) +static void x11font_cairo_setup( + unifont_drawctx *ctx, x11font_individual *xfi, Display *disp) { if (xfi->pixmap == None) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); XGCValues gcvals; GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget); int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin)); @@ -693,12 +702,12 @@ static void x11font_cairo_setup(unifont_drawctx *ctx, x11font_individual *xfi) } } -static void x11font_cairo_cache_glyph(x11font_individual *xfi, int glyphindex) +static void x11font_cairo_cache_glyph( + Display *disp, x11font_individual *xfi, int glyphindex) { XImage *image; int x, y; unsigned char *bitmap; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8, glyphindex & 0xFF); @@ -752,11 +761,10 @@ static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, } } -static void x11font_cairo_draw_16(unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, - const void *vstring, int start, int length) +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) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); const XChar2b *string = (const XChar2b *)vstring + start; int i; for (i = 0; i < length; i++) { @@ -768,7 +776,7 @@ static void x11font_cairo_draw_16(unifont_drawctx *ctx, XDrawImageString16(disp, xfi->pixmap, xfi->gc, xfi->pixoriginx, xfi->pixoriginy, string+i, 1); - x11font_cairo_cache_glyph(xfi, glyphindex); + x11font_cairo_cache_glyph(disp, xfi, glyphindex); } x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); x += XTextWidth16(xfi->xfs, string+i, 1); @@ -776,11 +784,10 @@ static void x11font_cairo_draw_16(unifont_drawctx *ctx, } } -static void x11font_cairo_draw_8(unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, - const void *vstring, int start, int length) +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) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); const char *string = (const char *)vstring + start; int i; for (i = 0; i < length; i++) { @@ -791,7 +798,7 @@ static void x11font_cairo_draw_8(unifont_drawctx *ctx, XDrawImageString(disp, xfi->pixmap, xfi->gc, xfi->pixoriginx, xfi->pixoriginy, string+i, 1); - x11font_cairo_cache_glyph(xfi, glyphindex); + x11font_cairo_cache_glyph(disp, xfi, glyphindex); } x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); x += XTextWidth(xfi->xfs, string+i, 1); @@ -803,9 +810,10 @@ static void x11font_cairo_draw_8(unifont_drawctx *ctx, 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); - void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, int x, int y, - 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); }; /* @@ -843,14 +851,14 @@ static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = { #endif }; -static void x11font_really_draw_text(const struct x11font_drawfuncs *dfns, - unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, - const void *string, int nchars, - int shadowoffset, - int fontvariable, int cellwidth) +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, centre; + int start = 0, step, nsteps; + bool centre; if (fontvariable) { /* @@ -859,26 +867,27 @@ static void x11font_really_draw_text(const struct x11font_drawfuncs *dfns, */ step = 1; nsteps = nchars; - centre = TRUE; + centre = true; } else { /* * In a fixed-pitch font, we can draw the whole lot in one go. */ step = nchars; nsteps = 1; - centre = FALSE; + centre = false; } - dfns->setup(ctx, xfi); + 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, X, y, string, start, step); + dfns->draw(ctx, xfi, disp, X, y, string, start, step); if (shadowoffset) - dfns->draw(ctx, xfi, X + shadowoffset, y, string, start, step); + dfns->draw(ctx, xfi, disp, X + shadowoffset, y, + string, start, step); x += cellwidth; start += step; @@ -887,16 +896,16 @@ static void x11font_really_draw_text(const struct x11font_drawfuncs *dfns, static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, int len, - int wide, int bold, int cellwidth) + bool wide, bool bold, int cellwidth) { - struct x11font *xfont = (struct x11font *)font; + 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 -= xfont->wide; - bold -= xfont->bold; + wide = wide && !xfont->wide; + bold = bold && !xfont->bold; /* * Decide which subfont we're using, and whether we have to @@ -904,13 +913,13 @@ static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, */ if (xfont->shadowalways && bold) { shadowoffset = xfont->shadowoffset; - bold = 0; + bold = false; } sfid = 2 * wide + bold; if (!xfont->fonts[sfid].allocated) x11_alloc_subfont(xfont, sfid); if (bold && !xfont->fonts[sfid].xfs) { - bold = 0; + bold = false; shadowoffset = xfont->shadowoffset; sfid = 2 * wide + bold; if (!xfont->fonts[sfid].allocated) @@ -935,7 +944,7 @@ static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, } x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx, - &xfont->fonts[sfid], x, y, + &xfont->fonts[sfid], xfont->disp, x, y, xcs, len, shadowoffset, xfont->variable, cellwidth * mult); sfree(xcs); @@ -946,9 +955,9 @@ 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, NULL); + sbstring, len+1, ".", NULL); x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, - &xfont->fonts[sfid], x, y, + &xfont->fonts[sfid], xfont->disp, x, y, sbstring, sblen, shadowoffset, xfont->variable, cellwidth * mult); sfree(sbstring); @@ -957,7 +966,8 @@ static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, - int len, int wide, int bold, int cellwidth) + int len, bool wide, bool bold, + int cellwidth) { /* * For server-side fonts, there's no sophisticated system for @@ -972,11 +982,14 @@ static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, static void x11font_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, void *callback_ctx) { - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + 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); @@ -1131,7 +1144,7 @@ static void x11font_enum_fonts(GtkWidget *widget, static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, int *size, int *flags, - int resolve_aliases) + bool resolve_aliases) { /* * When given an X11 font name to try to make sense of for a @@ -1145,10 +1158,13 @@ static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, * selector treats them as worthwhile in their own right. */ XFontStruct *xfs; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + Display *disp; Atom fontprop, fontprop2; unsigned long ret; + if ((disp = get_x11_display()) == NULL) + return NULL; + xfs = XLoadQueryFont(disp, name); if (!xfs) @@ -1190,8 +1206,8 @@ static char *x11font_scale_fontname(GtkWidget *widget, const char *name, static char *x11font_size_increment(unifont *font, int increment) { - struct x11font *xfont = (struct x11font *)font; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + 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; @@ -1288,32 +1304,31 @@ static char *x11font_size_increment(unifont *font, int increment) #define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */ #endif -static int pangofont_has_glyph(unifont *font, wchar_t glyph); +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, - int wide, int bold, int cellwidth); + 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, int wide, int bold, + int len, bool wide, bool bold, int cellwidth); static unifont *pangofont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways); + bool wide, bool bold, + int shadowoffset, bool shadowalways); static unifont *pangofont_create_fallback(GtkWidget *widget, int height, - int wide, int bold, - int shadowoffset, int shadowalways); + 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, - int resolve_aliases); + 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 { - struct unifont u; /* * Pango objects. */ @@ -1326,7 +1341,8 @@ struct pangofont { /* * Data passed in to unifont_create(). */ - int bold, shadowoffset, shadowalways; + 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 @@ -1334,9 +1350,11 @@ struct pangofont { */ int *widthcache; unsigned nwidthcache; + + struct unifont u; }; -static const struct unifont_vtable pangofont_vtable = { +static const struct UnifontVtable pangofont_vtable = { pangofont_create, pangofont_create_fallback, pangofont_destroy, @@ -1363,14 +1381,15 @@ static const struct unifont_vtable pangofont_vtable = { * if it doesn't. So we check that the font family is actually one * supported by Pango. */ -static int pangofont_check_desc_makes_sense(PangoContext *ctx, - PangoFontDescription *desc) +static bool pangofont_check_desc_makes_sense(PangoContext *ctx, + PangoFontDescription *desc) { #ifndef PANGO_PRE_1POINT6 PangoFontMap *map; #endif PangoFontFamily **families; - int i, nfamilies, matched; + int i, nfamilies; + bool matched; /* * Ask Pango for a list of font families, and iterate through @@ -1380,17 +1399,17 @@ static int pangofont_check_desc_makes_sense(PangoContext *ctx, #ifndef PANGO_PRE_1POINT6 map = pango_context_get_font_map(ctx); if (!map) - return FALSE; + return false; pango_font_map_list_families(map, &families, &nfamilies); #else pango_context_list_families(ctx, &families, &nfamilies); #endif - matched = FALSE; + 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; + matched = true; break; } } @@ -1402,8 +1421,8 @@ static int pangofont_check_desc_makes_sense(PangoContext *ctx, static unifont *pangofont_create_internal(GtkWidget *widget, PangoContext *ctx, PangoFontDescription *desc, - int wide, int bold, - int shadowoffset, int shadowalways) + bool wide, bool bold, + int shadowoffset, bool shadowalways) { struct pangofont *pfont; #ifndef PANGO_PRE_1POINT6 @@ -1443,7 +1462,7 @@ static unifont *pangofont_create_internal(GtkWidget *widget, pfont->u.ascent = PANGO_PIXELS(pango_font_metrics_get_ascent(metrics)); pfont->u.descent = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); pfont->u.height = pfont->u.ascent + pfont->u.descent; - pfont->u.want_fallback = FALSE; + pfont->u.want_fallback = false; #ifdef DRAW_TEXT_CAIRO pfont->u.preferred_drawtype = DRAWTYPE_CAIRO; #elif defined DRAW_TEXT_GDK @@ -1464,12 +1483,12 @@ static unifont *pangofont_create_internal(GtkWidget *widget, pango_font_metrics_unref(metrics); - return (unifont *)pfont; + return &pfont->u; } static unifont *pangofont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways) + bool wide, bool bold, + int shadowoffset, bool shadowalways) { PangoContext *ctx; PangoFontDescription *desc; @@ -1491,8 +1510,8 @@ static unifont *pangofont_create(GtkWidget *widget, const char *name, } static unifont *pangofont_create_fallback(GtkWidget *widget, int height, - int wide, int bold, - int shadowoffset, int shadowalways) + bool wide, bool bold, + int shadowoffset, bool shadowalways) { PangoContext *ctx; PangoFontDescription *desc; @@ -1512,11 +1531,11 @@ static unifont *pangofont_create_fallback(GtkWidget *widget, int height, static void pangofont_destroy(unifont *font) { - struct pangofont *pfont = (struct pangofont *)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(font); + sfree(pfont); } static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont, @@ -1548,10 +1567,10 @@ static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont, return pfont->widthcache[uchr]; } -static int pangofont_has_glyph(unifont *font, wchar_t glyph) +static bool pangofont_has_glyph(unifont *font, wchar_t glyph) { /* Pango implements font fallback, so assume it has everything */ - return TRUE; + return true; } #ifdef DRAW_TEXT_GDK @@ -1573,15 +1592,15 @@ static void pango_cairo_draw_layout(unifont_drawctx *ctx, static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, - int len, int wide, int bold, int cellwidth, - int combining) + int len, bool wide, bool bold, + int cellwidth, bool combining) { - struct pangofont *pfont = (struct pangofont *)font; + struct pangofont *pfont = container_of(font, struct pangofont, u); PangoLayout *layout; PangoRectangle rect; char *utfstring, *utfptr; int utflen; - int shadowbold = FALSE; + bool shadowbold = false; void (*draw_layout)(unifont_drawctx *ctx, gint x, gint y, PangoLayout *layout) = NULL; @@ -1603,9 +1622,9 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget)); pango_layout_set_font_description(layout, pfont->desc); - if (bold > pfont->bold) { + if (bold && !pfont->bold) { if (pfont->shadowalways) - shadowbold = TRUE; + shadowbold = true; else { PangoFontDescription *desc2 = pango_font_description_copy_static(pfont->desc); @@ -1620,7 +1639,7 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, */ utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */ utflen = wc_to_mb(CS_UTF8, 0, string, len, - utfstring, len*6+1, ".", NULL, NULL); + utfstring, len*6+1, ".", NULL); utfptr = utfstring; while (utflen > 0) { @@ -1729,15 +1748,15 @@ static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, int len, - int wide, int bold, int cellwidth) + bool wide, bool bold, int cellwidth) { pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, - cellwidth, FALSE); + cellwidth, false); } static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, - int len, int wide, int bold, + int len, bool wide, bool bold, int cellwidth) { wchar_t *tmpstring = NULL; @@ -1754,7 +1773,7 @@ static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, len++; } pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, - cellwidth, TRUE); + cellwidth, true); sfree(tmpstring); } @@ -1921,7 +1940,7 @@ static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback, static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name, int *size, int *flags, - int resolve_aliases) + bool resolve_aliases) { /* * When given a Pango font name to try to make sense of for a @@ -2007,7 +2026,7 @@ static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, static char *pangofont_size_increment(unifont *font, int increment) { - struct pangofont *pfont = (struct pangofont *)font; + struct pangofont *pfont = container_of(font, struct pangofont, u); PangoFontDescription *desc; int size; char *newname, *retname; @@ -2046,7 +2065,7 @@ static char *pangofont_size_increment(unifont *font, int increment) * * The 'multifont' subclass is omitted here, as discussed above. */ -static const struct unifont_vtable *unifont_types[] = { +static const struct UnifontVtable *unifont_types[] = { #if GTK_CHECK_VERSION(2,0,0) &pangofont_vtable, #endif @@ -2101,8 +2120,8 @@ static const char *unifont_do_prefix(const char *name, int *start, int *end) } } -unifont *unifont_create(GtkWidget *widget, const char *name, int wide, - int bold, int shadowoffset, int shadowalways) +unifont *unifont_create(GtkWidget *widget, const char *name, bool wide, + bool bold, int shadowoffset, bool shadowalways) { int i, start, end; @@ -2124,14 +2143,14 @@ void unifont_destroy(unifont *font) void unifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, int len, - int wide, int bold, int cellwidth) + 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, - int wide, int bold, int cellwidth) + bool wide, bool bold, int cellwidth) { font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold, cellwidth); @@ -2157,21 +2176,22 @@ char *unifont_size_increment(unifont *font, int increment) static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, int len, - int wide, int bold, int cellwidth); + 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, int wide, int bold, + 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 { - struct unifont u; unifont *main; unifont *fallback; + + struct unifont u; }; -static const struct unifont_vtable multifont_vtable = { +static const struct UnifontVtable multifont_vtable = { NULL, /* creation is done specially */ NULL, multifont_destroy, @@ -2186,8 +2206,8 @@ static const struct unifont_vtable multifont_vtable = { }; unifont *multifont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways) + bool wide, bool bold, + int shadowoffset, bool shadowalways) { int i; unifont *font, *fallback; @@ -2222,36 +2242,37 @@ unifont *multifont_create(GtkWidget *widget, const char *name, mfont->u.descent = font->descent; mfont->u.height = font->height; mfont->u.public_charset = font->public_charset; - mfont->u.want_fallback = FALSE; /* shouldn't be needed, but just in case */ + mfont->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 (unifont *)mfont; + return &mfont->u; } static void multifont_destroy(unifont *font) { - struct multifont *mfont = (struct multifont *)font; + struct multifont *mfont = container_of(font, struct multifont, u); unifont_destroy(mfont->main); if (mfont->fallback) unifont_destroy(mfont->fallback); - sfree(font); + sfree(mfont); } typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, - int len, int wide, int bold, + 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, - int wide, int bold, int cellwidth, + bool wide, bool bold, int cellwidth, int cellinc, unifont_draw_func_t draw) { - struct multifont *mfont = (struct multifont *)font; + struct multifont *mfont = container_of(font, struct multifont, u); unifont *f; - int ok, i; + bool ok; + int i; while (len > 0) { /* @@ -2278,7 +2299,7 @@ static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x, static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, int len, - int wide, int bold, int cellwidth) + bool wide, bool bold, int cellwidth) { multifont_draw_main(ctx, font, x, y, string, len, wide, bold, cellwidth, cellwidth, unifont_draw_text); @@ -2286,7 +2307,7 @@ static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, int x, int y, const wchar_t *string, - int len, int wide, int bold, + int len, bool wide, bool bold, int cellwidth) { multifont_draw_main(ctx, font, x, y, string, len, wide, bold, @@ -2295,7 +2316,7 @@ static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, static char *multifont_size_increment(unifont *font, int increment) { - struct multifont *mfont = (struct multifont *)font; + struct multifont *mfont = container_of(font, struct multifont, u); return unifont_size_increment(mfont->main, increment); } @@ -2309,8 +2330,6 @@ static char *multifont_size_increment(unifont *font, int increment) typedef struct fontinfo fontinfo; typedef struct unifontsel_internal { - /* This must be the structure's first element, for cross-casting */ - unifontsel u; GtkListStore *family_model, *style_model, *size_model; GtkWidget *family_list, *style_list, *size_entry, *size_list; GtkWidget *filter_buttons[4]; @@ -2325,7 +2344,9 @@ typedef struct unifontsel_internal { tree234 *fonts_by_realname, *fonts_by_selorder; fontinfo *selected; int selsize, intendedsize; - int inhibit_response; /* inhibit callbacks when we change GUI controls */ + bool inhibit_response; /* inhibit callbacks when we change GUI controls */ + + unifontsel u; } unifontsel_internal; /* @@ -2351,7 +2372,7 @@ struct fontinfo { /* * The class of font. */ - const struct unifont_vtable *fontclass; + const struct UnifontVtable *fontclass; }; struct fontinfo_realname_find { @@ -2444,8 +2465,8 @@ 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); + gtk_widget_set_sensitive(fs->u.ok_button, false); + gtk_widget_set_sensitive(fs->size_entry, false); unifontsel_draw_preview_text(fs); } @@ -2457,7 +2478,7 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs) int currflags = -1; fontinfo *info; - fs->inhibit_response = TRUE; + fs->inhibit_response = true; gtk_list_store_clear(fs->family_model); listindex = 0; @@ -2510,20 +2531,21 @@ static void unifontsel_setup_familylist(unifontsel_internal *fs) if (fs->selected && fs->selected->familyindex < 0) unifontsel_deselect(fs); - fs->inhibit_response = FALSE; + 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, started = FALSE; + 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; + started = false; /* * Search through the font tree for anything matching our @@ -2551,12 +2573,12 @@ static void unifontsel_setup_stylelist(unifontsel_internal *fs, * We've either finished a style/charset, or started a * new one, or both. */ - started = TRUE; + 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); + 3, true, 4, PANGO_WEIGHT_NORMAL, -1); listindex++; } if (info) { @@ -2565,7 +2587,7 @@ static void unifontsel_setup_stylelist(unifontsel_internal *fs, 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); + 3, false, 4, PANGO_WEIGHT_BOLD, -1); listindex++; } currcs = info->charset; @@ -2652,7 +2674,7 @@ static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx, (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); + false, false, 0, 0); } else font = NULL; @@ -2703,11 +2725,11 @@ static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx, info->fontclass->draw_text(dctx, font, 0, font->ascent, L"bankrupt jilted showmen quiz convex fogey", - 41, FALSE, FALSE, font->width); + 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); + 41, false, false, font->width); /* * The ordering of punctuation here is also selected * with some specific aims in mind. I put ` and ' @@ -2723,7 +2745,7 @@ static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx, info->fontclass->draw_text(dctx, font, 0, font->ascent + font->height * 2, L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", - 42, FALSE, FALSE, font->width); + 42, false, false, font->width); info->fontclass->destroy(font); } @@ -2791,12 +2813,12 @@ static void unifontsel_draw_preview_text(unifontsel_internal *fs) #endif gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area), - NULL, FALSE); + NULL, false); } static void unifontsel_select_font(unifontsel_internal *fs, fontinfo *info, int size, int leftlist, - int size_is_explicit) + bool size_is_explicit) { int index; int minval, maxval; @@ -2804,14 +2826,14 @@ static void unifontsel_select_font(unifontsel_internal *fs, GtkTreePath *treepath; GtkTreeIter iter; - fs->inhibit_response = TRUE; + 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); + gtk_widget_set_sensitive(fs->u.ok_button, true); /* * Find the index of this fontinfo in the selorder list. @@ -2840,7 +2862,7 @@ static void unifontsel_select_font(unifontsel_internal *fs, (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); + treepath, NULL, false, 0.0, 0.0); success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, treepath); assert(success); @@ -2864,7 +2886,7 @@ static void unifontsel_select_font(unifontsel_internal *fs, (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); + 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); @@ -2887,7 +2909,7 @@ static void unifontsel_select_font(unifontsel_internal *fs, (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); + treepath, NULL, false, 0.0, 0.0); gtk_tree_path_free(treepath); size = info->size; } else { @@ -2896,9 +2918,9 @@ static void unifontsel_select_font(unifontsel_internal *fs, 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); + treepath, NULL, false); gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list), - treepath, NULL, FALSE, 0.0, + treepath, NULL, false, 0.0, 0.0); gtk_tree_path_free(treepath); } @@ -2928,13 +2950,13 @@ static void unifontsel_select_font(unifontsel_internal *fs, unifontsel_draw_preview_text(fs); - fs->inhibit_response = FALSE; + fs->inhibit_response = false; } static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data) { unifontsel_internal *fs = (unifontsel_internal *)data; - int newstate = gtk_toggle_button_get_active(tb); + bool newstate = gtk_toggle_button_get_active(tb); int newflags; int flagbit = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tb), "user-data")); @@ -2954,7 +2976,7 @@ static void unifontsel_add_entry(void *ctx, const char *realfontname, const char *family, const char *charset, const char *style, const char *stylekey, int size, int flags, - const struct unifont_vtable *fontclass) + const struct UnifontVtable *fontclass) { unifontsel_internal *fs = (unifontsel_internal *)ctx; fontinfo *info; @@ -3113,7 +3135,7 @@ static void family_changed(GtkTreeSelection *treeselection, gpointer data) if (!info->size) fs->selsize = fs->intendedsize; /* font is scalable */ unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, - 1, FALSE); + 1, false); } static void style_changed(GtkTreeSelection *treeselection, gpointer data) @@ -3140,7 +3162,7 @@ static void style_changed(GtkTreeSelection *treeselection, gpointer data) if (!info->size) fs->selsize = fs->intendedsize; /* font is scalable */ unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize, - 2, FALSE); + 2, false); } static void size_changed(GtkTreeSelection *treeselection, gpointer data) @@ -3159,7 +3181,7 @@ static void size_changed(GtkTreeSelection *treeselection, gpointer data) gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1); info = (fontinfo *)index234(fs->fonts_by_selorder, minval); - unifontsel_select_font(fs, info, info->size ? info->size : size, 3, TRUE); + unifontsel_select_font(fs, info, info->size ? info->size : size, 3, true); } static void size_entry_changed(GtkEditable *ed, gpointer data) @@ -3176,7 +3198,7 @@ static void size_entry_changed(GtkEditable *ed, gpointer data) if (size > 0) { assert(fs->selected->size == 0); - unifontsel_select_font(fs, fs->selected, size, 3, TRUE); + unifontsel_select_font(fs, fs->selected, size, 3, true); } } @@ -3200,7 +3222,7 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, struct fontinfo_realname_find f; newname = info->fontclass->canonify_fontname - (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, TRUE); + (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true); f.realname = newname; f.flags = flags; @@ -3213,7 +3235,7 @@ static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path, return; /* didn't change under canonification => not an alias */ unifontsel_select_font(fs, newinfo, newinfo->size ? newinfo->size : newsize, - 1, TRUE); + 1, true); } } @@ -3228,7 +3250,7 @@ static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) dctx.u.cairo.cr = cr; unifontsel_draw_preview_text_inner(&dctx, fs); - return TRUE; + return true; } #else static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, @@ -3250,7 +3272,7 @@ static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, unifontsel_draw_preview_text(fs); #endif - return TRUE; + return true; } #endif @@ -3283,9 +3305,9 @@ static gint unifontsel_configure_area(GtkWidget *widget, } #endif - gdk_window_invalidate_rect(gtk_widget_get_window(widget), NULL, FALSE); + gdk_window_invalidate_rect(gtk_widget_get_window(widget), NULL, false); - return TRUE; + return true; } unifontsel *unifontsel_new(const char *wintitle) @@ -3297,7 +3319,7 @@ unifontsel *unifontsel_new(const char *wintitle) int lists_height, preview_height, font_width, style_width, size_width; int i; - fs->inhibit_response = FALSE; + fs->inhibit_response = false; fs->selected = NULL; { @@ -3340,7 +3362,7 @@ unifontsel *unifontsel_new(const char *wintitle) table = gtk_grid_new(); gtk_grid_set_column_spacing(GTK_GRID(table), 8); #else - table = gtk_table_new(8, 3, FALSE); + table = gtk_table_new(8, 3, false); gtk_table_set_col_spacings(GTK_TABLE(table), 8); #endif gtk_widget_show(table); @@ -3363,14 +3385,14 @@ unifontsel *unifontsel_new(const char *wintitle) gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area (GTK_DIALOG(fs->u.window))), - w, TRUE, TRUE, 0); + 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); + 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 @@ -3382,7 +3404,7 @@ unifontsel *unifontsel_new(const char *wintitle) */ 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_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 @@ -3405,7 +3427,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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); @@ -3418,7 +3440,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3433,7 +3455,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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_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 @@ -3454,7 +3476,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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); @@ -3467,7 +3489,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3483,7 +3505,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3492,7 +3514,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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_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(), @@ -3511,7 +3533,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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); @@ -3533,9 +3555,9 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + false, false); gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg, - FALSE, FALSE); + false, false); #endif #if GTK_CHECK_VERSION(3,0,0) g_signal_connect(G_OBJECT(fs->preview_area), "draw", @@ -3572,7 +3594,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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); @@ -3595,7 +3617,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3608,7 +3630,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3621,7 +3643,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3635,7 +3657,7 @@ unifontsel *unifontsel_new(const char *wintitle) 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); + 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 @@ -3661,14 +3683,14 @@ unifontsel *unifontsel_new(const char *wintitle) unifontsel_setup_familylist(fs); fs->selsize = fs->intendedsize = 13; /* random default */ - gtk_widget_set_sensitive(fs->u.ok_button, FALSE); + gtk_widget_set_sensitive(fs->u.ok_button, false); - return (unifontsel *)fs; + return &fs->u; } void unifontsel_destroy(unifontsel *fontsel) { - unifontsel_internal *fs = (unifontsel_internal *)fontsel; + unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u); fontinfo *info; #ifndef NO_BACKING_PIXMAPS @@ -3687,7 +3709,7 @@ void unifontsel_destroy(unifontsel *fontsel) void unifontsel_set_name(unifontsel *fontsel, const char *fontname) { - unifontsel_internal *fs = (unifontsel_internal *)fontsel; + unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u); int i, start, end, size, flags; const char *fontname2 = NULL; fontinfo *info; @@ -3704,7 +3726,7 @@ 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); + (GTK_WIDGET(fs->u.window), fontname, &size, &flags, false); if (fontname2) break; } @@ -3742,12 +3764,12 @@ void unifontsel_set_name(unifontsel *fontsel, const char *fontname) * know everything we need to fill in all the fields in the * dialog. */ - unifontsel_select_font(fs, info, size, 0, TRUE); + unifontsel_select_font(fs, info, size, 0, true); } char *unifontsel_get_name(unifontsel *fontsel) { - unifontsel_internal *fs = (unifontsel_internal *)fontsel; + unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u); char *name; if (!fs->selected) diff --git a/unix/gtkfont.h b/unix/gtkfont.h index 35ce62e1..ff91929e 100644 --- a/unix/gtkfont.h +++ b/unix/gtkfont.h @@ -49,9 +49,9 @@ /* * Exports from gtkfont.c. */ -struct unifont_vtable; /* contents internal to gtkfont.c */ +struct UnifontVtable; /* contents internal to gtkfont.c */ typedef struct unifont { - const struct unifont_vtable *vt; + const struct UnifontVtable *vt; /* * `Non-static data members' of the `class', accessible to * external code. @@ -74,7 +74,7 @@ typedef struct unifont { * missing glyphs from other fonts), or whether it would like a * fallback font to cope with missing glyphs. */ - int want_fallback; + bool want_fallback; /* * Preferred drawing API to use when this class of font is active. @@ -134,18 +134,18 @@ typedef struct unifont_drawctx { } unifont_drawctx; unifont *unifont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways); + 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, - int wide, int bold, int cellwidth); + 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, - int wide, int bold, int cellwidth); + 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); @@ -159,8 +159,8 @@ char *unifont_size_increment(unifont *font, int increment); * as if it were an ordinary unifont. */ unifont *multifont_create(GtkWidget *widget, const char *name, - int wide, int bold, - int shadowoffset, int shadowalways); + bool wide, bool bold, + int shadowoffset, bool shadowalways); /* * Unified font selector dialog. I can't be bothered to do a diff --git a/unix/gtkmain.c b/unix/gtkmain.c index c80da702..6a3d25ed 100644 --- a/unix/gtkmain.c +++ b/unix/gtkmain.c @@ -41,21 +41,21 @@ #include #include #include +#include "x11misc.h" #endif static char *progname, **gtkargvstart; static int ngtkargs; -extern char **pty_argv; /* declared in pty.c */ -extern int use_pty_argv; - static const char *app_name = "pterm"; char *x_get_default(const char *key) { #ifndef NOT_X_WINDOWS - return XGetDefault(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), - app_name, key); + Display *disp; + if ((disp = get_x11_display()) == NULL) + return NULL; + return XGetDefault(disp, app_name, key); #else return NULL; #endif @@ -153,8 +153,8 @@ void launch_duplicate_session(Conf *conf) * into a byte stream, create a pipe, and send this byte stream * to the child through the pipe. */ - int i, ret, sersize, size; - char *data; + int i, ret; + strbuf *serialised; char option[80]; int pipefd[2]; @@ -163,35 +163,27 @@ void launch_duplicate_session(Conf *conf) return; } - size = sersize = conf_serialised_size(conf); - if (use_pty_argv && pty_argv) { - for (i = 0; pty_argv[i]; i++) - size += strlen(pty_argv[i]) + 1; - } + serialised = strbuf_new(); - data = snewn(size, char); - conf_serialise(conf, data); - if (use_pty_argv && pty_argv) { - int p = sersize; - for (i = 0; pty_argv[i]; i++) { - strcpy(data + p, pty_argv[i]); - p += strlen(pty_argv[i]) + 1; - } - assert(p == size); - } + 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,%d]", pipefd[0], size); + sprintf(option, "---[%d,%d]", pipefd[0], serialised->len); noncloexec(pipefd[0]); fork_and_exec_self(pipefd[1], option, NULL); close(pipefd[0]); i = ret = 0; - while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 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]); - sfree(data); + strbuf_free(serialised); } void launch_new_session(void) @@ -206,8 +198,9 @@ void launch_saved_session(const char *str) int read_dupsession_data(Conf *conf, char *arg) { - int fd, i, ret, size, size_used; + 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); @@ -227,35 +220,36 @@ int read_dupsession_data(Conf *conf, char *arg) exit(1); } - size_used = conf_deserialise(conf, data, size); - if (use_pty_argv && size > size_used) { - int n = 0; - i = size_used; - while (i < size) { - while (i < size && data[i]) i++; - if (i >= size) { - fprintf(stderr, "%s: malformed Duplicate Session data\n", - appname); - exit(1); - } - i++; - n++; - } - pty_argv = snewn(n+1, char *); - pty_argv[n] = NULL; - n = 0; - i = size_used; - while (i < size) { - char *p = data + i; - while (i < size && data[i]) i++; - assert(i < size); - i++; - pty_argv[n++] = dupstr(p); - } + 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; - sfree(data); + 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; } @@ -296,14 +290,28 @@ static void version(FILE *fp) { sfree(buildinfo_text); } -static struct gui_data *the_inst; - static const char *geometry_string; -int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, - Conf *conf) +void cmdline_error(const char *p, ...) { - int err = 0; + 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; /* @@ -316,7 +324,7 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, */ #define EXPECTS_ARG { \ if (--argc <= 0) { \ - err = 1; \ + err = true; \ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ continue; \ } else \ @@ -406,14 +414,14 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, { #if GTK_CHECK_VERSION(3,0,0) GdkRGBA rgba; - int success = gdk_rgba_parse(&rgba, val); + bool success = gdk_rgba_parse(&rgba, val); #else GdkColor col; - int success = gdk_color_parse(val, &col); + bool success = gdk_color_parse(val, &col); #endif if (!success) { - err = 1; + err = true; fprintf(stderr, "%s: unable to parse colour \"%s\"\n", appname, val); } else { @@ -456,7 +464,7 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, pty_argv[argc] = NULL; break; /* finished command-line processing */ } else - err = 1, fprintf(stderr, "%s: -e expects an argument\n", + err = true, fprintf(stderr, "%s: -e expects an argument\n", appname); } else if (!strcmp(p, "-title")) { @@ -475,31 +483,31 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_stamp_utmp, 0); + conf_set_bool(conf, CONF_stamp_utmp, false); } else if (!strcmp(p, "-ut")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_stamp_utmp, 1); + conf_set_bool(conf, CONF_stamp_utmp, true); } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_login_shell, 0); + conf_set_bool(conf, CONF_login_shell, false); } else if (!strcmp(p, "-ls")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_login_shell, 1); + conf_set_bool(conf, CONF_login_shell, true); } else if (!strcmp(p, "-nethack")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_nethack_keypad, 1); + conf_set_bool(conf, CONF_nethack_keypad, true); } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_scrollbar, 0); + conf_set_bool(conf, CONF_scrollbar, false); } else if (!strcmp(p, "-sb")) { SECOND_PASS_ONLY; - conf_set_int(conf, CONF_scrollbar, 1); + conf_set_bool(conf, CONF_scrollbar, true); } else if (!strcmp(p, "-name")) { EXPECTS_ARG; @@ -521,13 +529,16 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, pgp_fingerprints(); exit(1); - } else if(p[0] != '-' && (!do_everything || - process_nonoption_arg(p, conf, - allow_launch))) { - /* do nothing */ + } 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 = 1; + err = true; fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); } } @@ -535,28 +546,50 @@ int do_cmdline(int argc, char **argv, int do_everything, int *allow_launch, return err; } -GtkWidget *make_gtk_toplevel_window(void *frontend) +GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return gtk_window_new(GTK_WINDOW_TOPLEVEL); } -extern int cfgbox(Conf *conf); +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 if (result == 0) { + /* 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(); + } +} -const int buildinfo_gtk_relevant = TRUE; +void session_window_closed(void) +{ + gtk_main_quit(); +} int main(int argc, char **argv) { Conf *conf; - int need_config_box; + bool need_config_box; setlocale(LC_CTYPE, ""); - { - /* Call the function in ux{putty,pterm}.c to do app-type - * specific setup */ - extern void setup(int); - setup(TRUE); /* TRUE means we are a one-session process */ - } + /* 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]; @@ -584,51 +617,52 @@ int main(int argc, char **argv) * terminating the main pterm/PuTTY. However, we'll have to * unblock it again when pterm forks. */ - block_signal(SIGPIPE, 1); + block_signal(SIGPIPE, true); if (argc > 1 && !strncmp(argv[1], "---", 3)) { - extern const int dup_check_launchable; - 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; + need_config_box = false; } else { - /* By default, we bring up the config dialog, rather than launching - * a session. This gets set to TRUE if something happens to change - * that (e.g., a hostname is specified on the command-line). */ - int allow_launch = FALSE; - if (do_cmdline(argc, argv, 0, &allow_launch, conf)) + if (do_cmdline(argc, argv, false, conf)) exit(1); /* pre-defaults pass to get -class */ do_defaults(NULL, conf); - if (do_cmdline(argc, argv, 1, &allow_launch, conf)) + if (do_cmdline(argc, argv, true, conf)) exit(1); /* post-defaults, do everything */ cmdline_run_saved(conf); - if (loaded_session) - allow_launch = TRUE; - - need_config_box = (!allow_launch || !conf_launchable(conf)); + if (cmdline_tooltype & TOOLTYPE_HOST_ARG) + need_config_box = !cmdline_host_ok(conf); + else + need_config_box = false; } - /* - * Put up the config box. - */ - if (need_config_box && !cfgbox(conf)) - exit(0); /* config box hit Cancel */ - - /* - * Create the main session window. We don't really need to keep - * the return value - the fact that it'll be linked from a zillion - * GTK and glib bits and bobs known to the main loop will be - * sufficient to make everything actually happen - but we stash it - * in a global variable anyway, so that it'll be easy to find in a - * debugger. - */ - the_inst = new_session_window(conf, geometry_string); + 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(); diff --git a/unix/gtkmisc.c b/unix/gtkmisc.c index 23f26d07..c0c9682a 100644 --- a/unix/gtkmisc.c +++ b/unix/gtkmisc.c @@ -15,6 +15,11 @@ #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) { /* @@ -125,7 +130,7 @@ 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); + w, true, true, 0); #elif !GTK_CHECK_VERSION(3,0,0) @@ -144,14 +149,14 @@ void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w) #endif gtk_widget_show(align); gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - align, FALSE, TRUE, 0); + 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); + 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); + g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL); #else /* GTK 3 */ @@ -161,10 +166,10 @@ void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w) GtkWidget *sep; g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); - gtk_box_pack_end(vbox, w, FALSE, TRUE, 0); + gtk_box_pack_end(vbox, w, false, true, 0); sep = gtk_hseparator_new(); - gtk_box_pack_end(vbox, sep, FALSE, TRUE, 0); + gtk_box_pack_end(vbox, sep, false, true, 0); gtk_widget_show(sep); #endif @@ -185,8 +190,7 @@ GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg) } void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, - gboolean expand, gboolean fill, - guint padding) + bool expand, bool fill, guint padding) { #if GTK_CHECK_VERSION(3,0,0) /* GtkWindow is a GtkBin, hence contains exactly one child, which @@ -206,3 +210,14 @@ 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/gtkmisc.h b/unix/gtkmisc.h index e670d9d4..3d2d1f36 100644 --- a/unix/gtkmisc.h +++ b/unix/gtkmisc.h @@ -12,8 +12,7 @@ void align_label_left(GtkLabel *label); GtkWidget *our_dialog_new(void); void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, - gboolean expand, gboolean fill, - guint padding); + bool expand, bool fill, guint padding); void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w); GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg); diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 69e33509..369e9942 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -51,17 +51,56 @@ #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) GdkAtom compound_text_atom, utf8_string_atom; +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_data_instance; +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 +}; -struct gui_data { +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 @@ -94,43 +133,42 @@ struct gui_data { GtkIMContext *imc; #endif unifont *fonts[4]; /* normal, bold, wide, widebold */ - int xpos, ypos, gotpos, gravity; + int xpos, ypos, gravity; + bool gotpos; GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; GdkColor cols[NALLCOLOURS]; #if !GTK_CHECK_VERSION(3,0,0) GdkColormap *colmap; #endif - int direct_to_font; - wchar_t *pastein_data; - int pastein_data_len; + bool direct_to_font; + struct clipboard_state clipstates[N_CLIPBOARDS]; #ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - GtkClipboard *clipboard; - 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; + /* 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; - int ignore_sbar; - int mouseptr_visible; - int busy_status; + 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; - void *ldisc; - Backend *back; - void *backhandle; + Ldisc *ldisc; + Backend *backend; Terminal *term; - void *logctx; - int exited; + LogContext *logctx; + bool exited; struct unicode_data ucsdata; Conf *conf; - void *eventlogstuff; + eventlog_stuff *eventlogstuff; guint32 input_event_time; /* Timestamp of the most recent input event. */ - int reconfiguring; + GtkWidget *dialogs[DIALOG_SLOT_LIMIT]; #if GTK_CHECK_VERSION(3,4,0) gdouble cumulative_scroll; #endif @@ -140,47 +178,92 @@ struct gui_data { int cursor_type; int drawtype; int meta_mod_mask; +#ifdef OSX_META_KEY_CONFIG + int system_mod_mask; +#endif + bool send_raw_mouse; + unifont_drawctx uctx; + + Seat seat; + TermWin termwin; + LogPolicy logpolicy; }; -static void cache_conf_values(struct gui_data *inst) +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_int(inst->conf, CONF_osx_option_meta)) + if (conf_get_bool(inst->conf, CONF_osx_option_meta)) inst->meta_mod_mask |= GDK_MOD1_MASK; - if (conf_get_int(inst->conf, CONF_osx_command_meta)) + 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 } -struct draw_ctx { - struct gui_data *inst; - unifont_drawctx uctx; -}; +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 int send_raw_mouse; +static void post_fatal_message_box_toplevel(void *vctx) +{ + GtkFrontend *inst = (GtkFrontend *)vctx; + gtk_widget_destroy(inst->window); +} -static void start_backend(struct gui_data *inst); -static void exit_callback(void *vinst); +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); +} -void connection_fatal(void *frontend, const char *p, ...) +static void common_connfatal_message_box( + GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn) { - struct gui_data *inst = (struct gui_data *)frontend; + char *title = dupcat(appname, " Fatal Error", NULL); + 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); +} - va_list ap; - char *msg; - va_start(ap, p); - msg = dupvprintf(p, ap); - va_end(ap); - fatal_message_box(inst->window, msg); - sfree(msg); +void fatal_message_box(GtkFrontend *inst, const char *msg) +{ + common_connfatal_message_box(inst, msg, post_fatal_message_box); +} - queue_toplevel_callback(exit_callback, inst); +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); } /* @@ -209,72 +292,125 @@ char *platform_default_s(const char *name) return NULL; } -int platform_default_i(const char *name, int def) +bool platform_default_b(const char *name, bool def) { - if (!strcmp(name, "CloseOnExit")) - return 2; /* maps to FORCE_ON after painful rearrangement :-( */ - if (!strcmp(name, "WinNameAlways")) - return 0; /* X natively supports icon titles, so use 'em by default */ + if (!strcmp(name, "WinNameAlways")) { + /* X natively supports icon titles, so use 'em by default */ + return false; + } return def; } -/* Dummy routine, only required in plink. */ -void frontend_echoedit_update(void *frontend, int echo, int edit) +int platform_default_i(const char *name, int def) { + if (!strcmp(name, "CloseOnExit")) + return 2; /* maps to FORCE_ON after painful rearrangement :-( */ + return def; } -char *get_ttymode(void *frontend, const char *mode) +static char *gtk_seat_get_ttymode(Seat *seat, const char *mode) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); return term_get_ttymode(inst->term, mode); } -int from_backend(void *frontend, int is_stderr, const char *data, int len) +static int gtk_seat_output(Seat *seat, bool is_stderr, + const void *data, int len) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); return term_data(inst->term, is_stderr, data, len); } -int from_backend_untrusted(void *frontend, const char *data, int len) -{ - struct gui_data *inst = (struct gui_data *)frontend; - return term_data_untrusted(inst->term, data, len); -} - -int from_backend_eof(void *frontend) +static bool gtk_seat_eof(Seat *seat) { - return TRUE; /* do respond to incoming EOF with outgoing */ + /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */ + return true; /* do respond to incoming EOF with outgoing */ } -int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen) +static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p, + bufchain *input) { - struct gui_data *inst = (struct gui_data *)p->frontend; + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); int ret; - ret = cmdline_get_passwd_input(p, in, inlen); + ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = term_get_userpass_input(inst->term, p, in, inlen); + ret = term_get_userpass_input(inst->term, p, input); return ret; } -void logevent(void *frontend, const char *string) +static bool gtk_seat_is_utf8(Seat *seat) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + return win_is_utf8(&inst->termwin); +} - log_eventlog(inst->logctx, string); +static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + win_get_pixels(&inst->termwin, w, h); + return true; +} + +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 const SeatVtable gtk_seat_vt = { + gtk_seat_output, + gtk_seat_eof, + gtk_seat_get_userpass_input, + gtk_seat_notify_remote_exit, + gtk_seat_connection_fatal, + gtk_seat_update_specials_menu, + gtk_seat_get_ttymode, + gtk_seat_set_busy_status, + gtk_seat_verify_ssh_host_key, + gtk_seat_confirm_weak_crypto_primitive, + gtk_seat_confirm_weak_cached_hostkey, + gtk_seat_is_utf8, + nullseat_echoedit_update, + gtk_seat_get_x_display, +#ifdef NOT_X_WINDOWS + nullseat_get_windowid, +#else + gtk_seat_get_windowid, +#endif + gtk_seat_get_window_pixel_size, +}; +static void gtk_eventlog(LogPolicy *lp, const char *string) +{ + GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); logevent_dlg(inst->eventlogstuff, string); } -int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */ +static int gtk_askappend(LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), void *ctx) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); + return gtkdlg_askappend(&inst->seat, filename, callback, ctx); +} - if (which) - return inst->font_height; - else - return inst->font_width; +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(&inst->seat, event, strlen(event)); + seat_stderr(&inst->seat, "\r\n", 2); } +static const LogPolicyVtable gtk_logpolicy_vt = { + gtk_eventlog, + gtk_askappend, + gtk_logging_error, +}; + /* * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) * into a cooked one (SELECT, EXTEND, PASTE). @@ -286,8 +422,6 @@ int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */ */ static Mouse_Button translate_button(Mouse_Button button) { - /* struct gui_data *inst = (struct gui_data *)frontend; */ - if (button == MBT_LEFT) return MBT_SELECT; if (button == MBT_MIDDLE) @@ -301,24 +435,48 @@ static Mouse_Button translate_button(Mouse_Button button) * Return the top-level GtkWindow associated with a particular * front end instance. */ -void *get_window(void *frontend) +GtkWidget *gtk_seat_get_window(Seat *seat) { - struct gui_data *inst = (struct gui_data *)frontend; + 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. */ -void set_iconic(void *frontend, int iconic) +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) - struct gui_data *inst = (struct gui_data *)frontend; - if (iconic) + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (minimised) gtk_window_iconify(GTK_WINDOW(inst->window)); else gtk_window_deiconify(GTK_WINDOW(inst->window)); @@ -328,9 +486,9 @@ void set_iconic(void *frontend, int iconic) /* * Move the window in response to a server-side request. */ -void move_window(void *frontend, int x, int y) +static void gtkwin_move(TermWin *tw, int x, int y) { - struct gui_data *inst = (struct gui_data *)frontend; + 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, @@ -349,9 +507,9 @@ void move_window(void *frontend, int x, int y) * Move the window to the top or bottom of the z-order in response * to a server-side request. */ -void set_zorder(void *frontend, int top) +static void gtkwin_set_zorder(TermWin *tw, bool top) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); if (top) gdk_window_raise(gtk_widget_get_window(inst->window)); else @@ -361,9 +519,9 @@ void set_zorder(void *frontend, int top) /* * Refresh the window in response to a server-side request. */ -void refresh_window(void *frontend) +static void gtkwin_refresh(TermWin *tw) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); term_invalidate(inst->term); } @@ -371,14 +529,14 @@ void refresh_window(void *frontend) * Maximise or restore the window in response to a server-side * request. */ -void set_zoomed(void *frontend, int zoomed) +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) - struct gui_data *inst = (struct gui_data *)frontend; - if (zoomed) + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (maximised) gtk_window_maximize(GTK_WINDOW(inst->window)); else gtk_window_unmaximize(GTK_WINDOW(inst->window)); @@ -386,20 +544,20 @@ void set_zoomed(void *frontend, int zoomed) } /* - * Report whether the window is iconic, for terminal reports. + * Report whether the window is minimised, for terminal reports. */ -int is_iconic(void *frontend) +static bool gtkwin_is_minimised(TermWin *tw) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); return !gdk_window_is_viewable(gtk_widget_get_window(inst->window)); } /* * Report the window's position, for terminal reports. */ -void get_window_pos(void *frontend, int *x, int *y) +static void gtkwin_get_pos(TermWin *tw, int *x, int *y) { - struct gui_data *inst = (struct gui_data *)frontend; + 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, @@ -415,9 +573,9 @@ void get_window_pos(void *frontend, int *x, int *y) /* * Report the window's pixel size, for terminal reports. */ -void get_window_pixels(void *frontend, int *x, int *y) +static void gtkwin_get_pixels(TermWin *tw, int *x, int *y) { - struct gui_data *inst = (struct gui_data *)frontend; + 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, @@ -430,33 +588,83 @@ void get_window_pixels(void *frontend, int *x, int *y) #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; +} + /* * Return the window or icon title. */ -char *get_window_title(void *frontend, int icon) +static const char *gtkwin_get_title(TermWin *tw, bool icon) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); return icon ? inst->icontitle : inst->wintitle; } -gint delete_window(GtkWidget *widget, GdkEvent *event, gpointer data) +static void warn_on_close_callback(void *vctx, int result) { - struct gui_data *inst = (struct gui_data *)data; - if (!inst->exited && conf_get_int(inst->conf, CONF_warn_on_close)) { - if (!reallyclose(inst)) - return TRUE; + 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", NULL); + GtkWidget *dialog = create_message_box( + inst->window, title, + "Are you sure you want to close this session?", + 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); + } + return true; } - return FALSE; + return false; } -static void update_mouseptr(struct gui_data *inst) +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 (send_raw_mouse) { + } else if (inst->send_raw_mouse) { gdk_window_set_cursor(gtk_widget_get_window(inst->area), inst->rawcursor); } else { @@ -475,46 +683,77 @@ static void update_mouseptr(struct gui_data *inst) } } -static void show_mouseptr(struct gui_data *inst, int show) +static void show_mouseptr(GtkFrontend *inst, bool show) { - if (!conf_get_int(inst->conf, CONF_hide_mouseptr)) - show = 1; + if (!conf_get_bool(inst->conf, CONF_hide_mouseptr)) + show = true; inst->mouseptr_visible = show; update_mouseptr(inst); } -static void draw_backing_rect(struct gui_data *inst); +static void draw_backing_rect(GtkFrontend *inst); -gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) +static void drawing_area_setup(GtkFrontend *inst, int width, int height) { - struct gui_data *inst = (struct gui_data *)data; - int w, h, need_size = 0; + int w, h, new_scale; + bool need_size = false; /* - * See if the terminal size has changed, in which case we must - * let the terminal know. + * See if the terminal size has changed. */ - w = (event->width - 2*inst->window_border) / inst->font_width; - h = (event->height - 2*inst->window_border) / inst->font_height; + 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); - need_size = 1; + /* + * 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 + + /* + * 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; + { int backing_w = w * inst->font_width + 2*inst->window_border; int backing_h = h * inst->font_height + 2*inst->window_border; + backing_w *= inst->scale; + backing_h *= inst->scale; + #ifndef NO_BACKING_PIXMAPS if (inst->pixmap) { gdk_pixmap_unref(inst->pixmap); inst->pixmap = NULL; } - inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(widget), + inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area), backing_w, backing_h, -1); #endif @@ -539,32 +778,98 @@ gint configure_area(GtkWidget *widget, GdkEventConfigure *event, gpointer data) term_invalidate(inst->term); #if GTK_CHECK_VERSION(2,0,0) - gtk_im_context_set_client_window(inst->imc, gtk_widget_get_window(widget)); + 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); +} - return TRUE; +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 + +#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_dctx(struct draw_ctx *dctx) +static void cairo_setup_draw_ctx(GtkFrontend *inst) { - cairo_get_matrix(dctx->uctx.u.cairo.cr, - &dctx->uctx.u.cairo.origmatrix); - cairo_set_line_width(dctx->uctx.u.cairo.cr, 1.0); - cairo_set_line_cap(dctx->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE); - cairo_set_line_join(dctx->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER); + 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(dctx->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE); + 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) { - struct gui_data *inst = (struct gui_data *)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 @@ -574,6 +879,33 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) */ 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); @@ -581,14 +913,16 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) 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; + return true; } #else gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; #ifndef NO_BACKING_PIXMAPS /* @@ -620,7 +954,7 @@ gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) } #endif - return TRUE; + return true; } #endif @@ -638,22 +972,30 @@ char *dup_keyval_name(guint keyval) } #endif -static void change_font_size(struct gui_data *inst, int increment); +static void change_font_size(GtkFrontend *inst, int increment); +static void key_pressed(GtkFrontend *inst); gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; char output[256]; wchar_t ucsoutput[2]; - int ucsval, start, end, special, output_charset, use_ucsoutput; - int nethack_mode, app_keypad_mode; + int ucsval, start, end, output_charset; + bool special, use_ucsoutput; + bool nethack_mode, app_keypad_mode; + bool generated_something = false; + +#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; + special = use_ucsoutput = false; output_charset = CS_ISO8859_1; #ifdef KEY_EVENT_DIAGNOSTICS @@ -725,7 +1067,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) "hardware_keycode=%d is_modifier=%s string=[%s]\n", type_string, keyval_string, state_string, (int)event->hardware_keycode, - event->is_modifier ? "TRUE" : "FALSE", + event->is_modifier ? "true" : "false", string_string)); sfree(type_string); @@ -772,7 +1114,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - key release accepted by IM\n")); #endif - return TRUE; + return true; } else { #ifdef KEY_EVENT_DIAGNOSTICS debug((" - key release not accepted by IM\n")); @@ -846,7 +1188,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Ctrl->: increase font size\n")); #endif change_font_size(inst, +1); - return TRUE; + return true; } if (event->keyval == GDK_KEY_less && (event->state & GDK_CONTROL_MASK)) { @@ -854,20 +1196,29 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Ctrl-<: increase font size\n")); #endif change_font_size(inst, -1); - return TRUE; + 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; + return true; } if (event->keyval == GDK_KEY_Page_Up && (event->state & GDK_CONTROL_MASK)) { @@ -875,15 +1226,24 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Ctrl-PgUp scroll\n")); #endif term_scroll(inst->term, 0, -1); - return TRUE; + 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; + return true; } if (event->keyval == GDK_KEY_Page_Down && (event->state & GDK_CONTROL_MASK)) { @@ -891,27 +1251,138 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Ctrl-PgDn scroll\n")); #endif term_scroll(inst->term, 0, +1); - return TRUE; + return true; } /* - * Neither does Shift-Ins. + * 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\n")); + debug((" - Shift-Insert: paste from custom clipboard\n")); #endif - request_paste(inst); - return TRUE; + 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); - special = FALSE; - use_ucsoutput = FALSE; + 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; + } + } - nethack_mode = conf_get_int(inst->conf, CONF_nethack_keypad); + /* + * 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; + + nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad); app_keypad_mode = (inst->term->app_keypad_keys && - !conf_get_int(inst->conf, CONF_no_applic_k)); + !conf_get_bool(inst->conf, CONF_no_applic_k)); /* ALT+things gives leading Escape. */ output[0] = '\033'; @@ -985,7 +1456,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) event->keyval == GDK_KEY_KP_Page_Up)) { /* nethack mode; do nothing */ } else { - int try_filter = TRUE; + bool try_filter = true; #ifdef META_MANUAL_MASK if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) { @@ -1000,7 +1471,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Meta modifier requiring manual intervention, " "suppressing IM filtering\n")); #endif - try_filter = FALSE; + try_filter = false; } #endif @@ -1012,7 +1483,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - key press accepted by IM\n")); #endif - return TRUE; + return true; } else { #ifdef KEY_EVENT_DIAGNOSTICS debug((" - key press not accepted by IM\n")); @@ -1096,7 +1567,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - keysym_to_unicode gave %04x\n", (unsigned)ucsoutput[1])); #endif - use_ucsoutput = TRUE; + use_ucsoutput = true; end = 2; } else { output[lenof(output)-1] = '\0'; @@ -1136,7 +1607,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) sfree(keyval_name); } #endif - use_ucsoutput = TRUE; + use_ucsoutput = true; end = 2; } } @@ -1151,7 +1622,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Ctrl-` special case, translating as 1c\n")); #endif output[1] = '\x1C'; - use_ucsoutput = FALSE; + use_ucsoutput = false; end = 2; } @@ -1198,11 +1669,11 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (event->keyval == GDK_KEY_Break && (event->state & GDK_CONTROL_MASK)) { #ifdef KEY_EVENT_DIAGNOSTICS - debug((" - Ctrl-Break special case, sending TS_BRK\n")); + debug((" - Ctrl-Break special case, sending SS_BRK\n")); #endif - if (inst->back) - inst->back->special(inst->backhandle, TS_BRK); - return TRUE; + if (inst->backend) + backend_special(inst->backend, SS_BRK, 0); + return true; } /* We handle Return ourselves, because it needs to be flagged as @@ -1212,9 +1683,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Return special case, translating as 0d + special\n")); #endif output[1] = '\015'; - use_ucsoutput = FALSE; + use_ucsoutput = false; end = 2; - special = TRUE; + special = true; } /* Control-2, Control-Space and Control-@ are NUL */ @@ -1227,7 +1698,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Ctrl-{space,2,@} special case, translating as 00\n")); #endif output[1] = '\0'; - use_ucsoutput = FALSE; + use_ucsoutput = false; end = 2; } @@ -1240,35 +1711,35 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #endif output[1] = '\240'; output_charset = CS_ISO8859_1; - use_ucsoutput = FALSE; + 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_int(inst->conf, CONF_bksp_is_delete) ? + 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; + use_ucsoutput = false; end = 2; - special = TRUE; + 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_int(inst->conf, CONF_bksp_is_delete) ? + 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; + use_ucsoutput = false; end = 2; - special = TRUE; + special = true; } /* Shift-Tab is ESC [ Z */ @@ -1279,7 +1750,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - Shift-Tab, translating as ESC [ Z\n")); #endif end = 1 + sprintf(output+1, "\033[Z"); - use_ucsoutput = FALSE; + 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 @@ -1328,7 +1799,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - Nethack-mode key")); #endif - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } } @@ -1382,7 +1853,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) end = 1 + sprintf(output+1, "\033?%c", xkey); } else end = 1 + sprintf(output+1, "\033O%c", xkey); - use_ucsoutput = FALSE; + use_ucsoutput = false; #ifdef KEY_EVENT_DIAGNOSTICS debug((" - Application keypad mode key")); #endif @@ -1493,7 +1964,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - VT52 mode small keypad key")); #endif - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } @@ -1521,7 +1992,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - SCO mode function key")); #endif - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } if (funky_type == FUNKY_SCO && /* SCO small keypad */ @@ -1536,7 +2007,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - SCO mode small keypad key")); #endif - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } if ((inst->term->vt52_mode || funky_type == FUNKY_VT100P) && @@ -1559,7 +2030,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11 - offt); } - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } if (funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { @@ -1567,7 +2038,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - Linux mode F1-F5 function key")); #endif - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } if (funky_type == FUNKY_XTERM && code >= 11 && code <= 14) { @@ -1583,16 +2054,16 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #endif end = 1 + sprintf(output+1, "\x1BO%c", code + 'P' - 11); } - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } if ((code == 1 || code == 4) && - conf_get_int(inst->conf, CONF_rxvt_homeend)) { + conf_get_bool(inst->conf, CONF_rxvt_homeend)) { #ifdef KEY_EVENT_DIAGNOSTICS debug((" - rxvt style Home/End")); #endif end = 1 + sprintf(output+1, code == 1 ? "\x1B[H" : "\x1BOw"); - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } if (code) { @@ -1600,7 +2071,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) debug((" - ordinary function key encoding")); #endif end = 1 + sprintf(output+1, "\x1B[%d~", code); - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } } @@ -1627,7 +2098,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - arrow key")); #endif - use_ucsoutput = FALSE; + use_ucsoutput = false; goto done; } } @@ -1658,8 +2129,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) * should never matter. */ output[end] = '\0'; /* NUL-terminate */ + generated_something = true; if (inst->ldisc) - ldisc_send(inst->ldisc, output+start, -2, 1); + ldisc_send(inst->ldisc, output+start, -2, true); } else if (!inst->direct_to_font) { if (!use_ucsoutput) { #ifdef KEY_EVENT_DIAGNOSTICS @@ -1677,9 +2149,10 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) charset_to_localenc(output_charset), string_string)); sfree(string_string); #endif + generated_something = true; if (inst->ldisc) lpage_send(inst->ldisc, output_charset, output+start, - end-start, 1); + end-start, true); } else { #ifdef KEY_EVENT_DIAGNOSTICS char *string_string = dupstr(""); @@ -1701,8 +2174,9 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) * We generated our own Unicode key data from the * keysym, so use that instead. */ + generated_something = true; if (inst->ldisc) - luni_send(inst->ldisc, ucsoutput+start, end-start, 1); + luni_send(inst->ldisc, ucsoutput+start, end-start, true); } } else { /* @@ -1724,21 +2198,24 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) string_string)); sfree(string_string); #endif + generated_something = true; if (inst->ldisc) - ldisc_send(inst->ldisc, output+start, end-start, 1); + ldisc_send(inst->ldisc, output+start, end-start, true); } - show_mouseptr(inst, 0); + show_mouseptr(inst, false); term_seen_key_event(inst->term); } - return TRUE; + 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) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; #ifdef KEY_EVENT_DIAGNOSTICS char *string_string = dupstr(""); @@ -1756,21 +2233,23 @@ void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) #endif if (inst->ldisc) - lpage_send(inst->ldisc, CS_UTF8, str, strlen(str), 1); - show_mouseptr(inst, 0); + lpage_send(inst->ldisc, CS_UTF8, str, strlen(str), true); + show_mouseptr(inst, false); term_seen_key_event(inst->term); + key_pressed(inst); } #endif #define SCROLL_INCREMENT_LINES 5 #if GTK_CHECK_VERSION(3,4,0) -gboolean scroll_internal(struct gui_data *inst, gdouble delta, guint state, +gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state, gdouble ex, gdouble ey) { - int shift, ctrl, alt, x, y, raw_mouse_mode; + int x, y; + bool shift, ctrl, alt, raw_mouse_mode; - show_mouseptr(inst, 1); + show_mouseptr(inst, true); shift = state & GDK_SHIFT_MASK; ctrl = state & GDK_CONTROL_MASK; @@ -1779,9 +2258,9 @@ gboolean scroll_internal(struct gui_data *inst, gdouble delta, guint state, x = (ex - inst->window_border) / inst->font_width; y = (ey - inst->window_border) / inst->font_height; - raw_mouse_mode = - send_raw_mouse && !(shift && conf_get_int(inst->conf, - CONF_mouse_override)); + raw_mouse_mode = (inst->send_raw_mouse && + !(shift && conf_get_bool(inst->conf, + CONF_mouse_override))); inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES; @@ -1791,7 +2270,7 @@ gboolean scroll_internal(struct gui_data *inst, gdouble delta, guint state, term_scroll(inst->term, 0, scroll_lines); inst->cumulative_scroll -= scroll_lines; } - return TRUE; + return true; } else { int scroll_events = (int)(inst->cumulative_scroll / SCROLL_INCREMENT_LINES); @@ -1812,36 +2291,37 @@ gboolean scroll_internal(struct gui_data *inst, gdouble delta, guint state, MA_CLICK, x, y, shift, ctrl, alt); } } - return TRUE; + return true; } } #endif -static gboolean button_internal(struct gui_data *inst, GdkEventButton *event) +static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event) { - int shift, ctrl, alt, x, y, button, act, raw_mouse_mode; + bool shift, ctrl, alt, raw_mouse_mode; + int x, y, button, act; /* Remember the timestamp. */ inst->input_event_time = event->time; - show_mouseptr(inst, 1); + 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 = - send_raw_mouse && !(shift && conf_get_int(inst->conf, - CONF_mouse_override)); + 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; + return true; } if (event->button == 5 && event->type == GDK_BUTTON_PRESS) { term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES); - return TRUE; + return true; } } @@ -1852,7 +2332,7 @@ static gboolean button_internal(struct gui_data *inst, GdkEventButton *event) gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, event->button, event->time); #endif - return TRUE; + return true; } if (event->button == 1) @@ -1866,18 +2346,18 @@ static gboolean button_internal(struct gui_data *inst, GdkEventButton *event) else if (event->button == 5) button = MBT_WHEEL_DOWN; else - return FALSE; /* don't even know what button! */ + 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 */ + 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 */ + 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; @@ -1885,12 +2365,12 @@ static gboolean button_internal(struct gui_data *inst, GdkEventButton *event) term_mouse(inst->term, button, translate_button(button), act, x, y, shift, ctrl, alt); - return TRUE; + return true; } gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; return button_internal(inst, event); } @@ -1902,14 +2382,14 @@ gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) */ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; #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 - return FALSE; + return false; #else guint button; GdkEventButton *event_button; @@ -1920,10 +2400,10 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) else if (event->direction == GDK_SCROLL_DOWN) button = 5; else - return FALSE; + return false; event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS); - event_button->window = event->window; + 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; @@ -1931,11 +2411,11 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) event_button->axes = NULL; event_button->state = event->state; event_button->button = button; - event_button->device = event->device; + 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(event_button); + gdk_event_free((GdkEvent *)event_button); return ret; #endif } @@ -1943,13 +2423,14 @@ gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; - int shift, ctrl, alt, x, y, button; + GtkFrontend *inst = (GtkFrontend *)data; + bool shift, ctrl, alt; + int x, y, button; /* Remember the timestamp. */ inst->input_event_time = event->time; - show_mouseptr(inst, 1); + show_mouseptr(inst, true); shift = event->state & GDK_SHIFT_MASK; ctrl = event->state & GDK_CONTROL_MASK; @@ -1961,7 +2442,7 @@ gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) else if (event->state & GDK_BUTTON3_MASK) button = MBT_RIGHT; else - return FALSE; /* don't even know what button! */ + 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; @@ -1969,70 +2450,151 @@ gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) term_mouse(inst->term, button, translate_button(button), MA_DRAG, x, y, shift, ctrl, alt); - return TRUE; + return true; } -void frontend_keypress(void *handle) +static void key_pressed(GtkFrontend *inst) { - struct gui_data *inst = (struct gui_data *)handle; - /* * 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) - cleanup_exit(0); + gtk_widget_destroy(inst->window); } -static void exit_callback(void *vinst) +static void exit_callback(void *vctx) { - struct gui_data *inst = (struct gui_data *)vinst; + GtkFrontend *inst = (GtkFrontend *)vctx; int exitcode, close_on_exit; if (!inst->exited && - (exitcode = inst->back->exitcode(inst->backhandle)) >= 0) { - inst->exited = TRUE; + (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_main_quit(); /* just go */ - if (inst->ldisc) { - ldisc_free(inst->ldisc); - inst->ldisc = NULL; - } - inst->back->free(inst->backhandle); - inst->backhandle = NULL; - inst->back = NULL; - term_provide_resize_fn(inst->term, NULL, NULL); - update_specials_menu(inst); - gtk_widget_set_sensitive(inst->restartitem, TRUE); + (close_on_exit == AUTO && exitcode == 0)) { + gtk_widget_destroy(inst->window); + } } } -void notify_remote_exit(void *frontend) +static void gtk_seat_notify_remote_exit(Seat *seat) { - struct gui_data *inst = (struct gui_data *)frontend; - + 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; + } + +#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) { - gtk_main_quit(); + GtkFrontend *inst = (GtkFrontend *)data; + inst->window = NULL; + delete_inst(inst); + session_window_closed(); } gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; term_set_focus(inst->term, event->in); term_update(inst->term); - show_mouseptr(inst, 1); - return FALSE; + show_mouseptr(inst, true); + return false; } -void set_busy_status(void *frontend, int status) +static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); inst->busy_status = status; update_mouseptr(inst); } @@ -2040,23 +2602,23 @@ void set_busy_status(void *frontend, int status) /* * set or clear the "raw mouse message" mode */ -void set_raw_mouse_mode(void *frontend, int activate) +static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate) { - struct gui_data *inst = (struct gui_data *)frontend; - activate = activate && !conf_get_int(inst->conf, CONF_no_mouse_rep); - send_raw_mouse = activate; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + activate = activate && !conf_get_bool(inst->conf, CONF_no_mouse_rep); + inst->send_raw_mouse = activate; update_mouseptr(inst); } #if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(struct gui_data *inst, +static void compute_whole_window_size(GtkFrontend *inst, int wchars, int hchars, int *wpix, int *hpix); #endif -void request_resize(void *frontend, int w, int h) +static void gtkwin_request_resize(TermWin *tw, int w, int h) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); #if !GTK_CHECK_VERSION(3,0,0) @@ -2085,7 +2647,7 @@ void request_resize(void *frontend, int w, int h) * bogus size request which guarantees to be bigger than the * current size of the drawing area. */ - get_window_pixels(inst, &large_x, &large_y); + win_get_pixels(&inst->termwin, &large_x, &large_y); large_x += 32; large_y += 32; @@ -2143,7 +2705,7 @@ void request_resize(void *frontend, int w, int h) } -static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b) +static void real_palette_set(GtkFrontend *inst, int n, int r, int g, int b) { inst->cols[n].red = r * 0x0101; inst->cols[n].green = g * 0x0101; @@ -2154,7 +2716,7 @@ static void real_palette_set(struct gui_data *inst, int n, int r, int g, int b) gboolean success[1]; gdk_colormap_free_colors(inst->colmap, inst->cols + n, 1); gdk_colormap_alloc_colors(inst->colmap, inst->cols + n, 1, - FALSE, TRUE, success); + false, true, success); if (!success[0]) g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", appname, n, r, g, b); @@ -2188,16 +2750,16 @@ void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col) free(data); free(col_css); #else - if (gtk_widget_get_window(win)) { + 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(win), &col_mutable); + gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable); } #endif } -void set_window_background(struct gui_data *inst) +void set_window_background(GtkFrontend *inst) { if (inst->area) set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]); @@ -2205,9 +2767,9 @@ void set_window_background(struct gui_data *inst) set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]); } -void palette_set(void *frontend, int n, int r, int g, int b) +static void gtkwin_palette_set(TermWin *tw, int n, int r, int g, int b) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); if (n >= 16) n += 256 - 16; if (n >= NALLCOLOURS) @@ -2222,9 +2784,20 @@ void palette_set(void *frontend, int n, int r, int g, int b) } } -void palette_reset(void *frontend) +static bool gtkwin_palette_get(TermWin *tw, int n, int *r, int *g, int *b) +{ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + if (n < 0 || n >= NALLCOLOURS) + return false; + *r = inst->cols[n].red >> 8; + *g = inst->cols[n].green >> 8; + *b = inst->cols[n].blue >> 8; + return true; +} + +static void gtkwin_palette_reset(TermWin *tw) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); /* This maps colour indices in inst->conf to those used in inst->cols. */ static const int ww[] = { 256, 257, 258, 259, 260, 261, @@ -2270,7 +2843,7 @@ void palette_reset(void *frontend) { gboolean success[NALLCOLOURS]; gdk_colormap_alloc_colors(inst->colmap, inst->cols, NALLCOLOURS, - FALSE, TRUE, success); + false, true, success); for (i = 0; i < NALLCOLOURS; i++) { if (!success[i]) g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", @@ -2291,6 +2864,20 @@ void palette_reset(void *frontend) } } +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 /* ---------------------------------------------------------------------- @@ -2300,26 +2887,29 @@ void palette_reset(void *frontend) * formats it feels like. */ -void init_clipboard(struct gui_data *inst) +void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) { - inst->clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(), - DEFAULT_CLIPBOARD); + 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; + } } -/* - * 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 { - struct gui_data *inst; - char *pasteout_data_utf8; - int pasteout_data_utf8_len; -}; +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, @@ -2327,9 +2917,8 @@ static void clipboard_provide_data(GtkClipboard *clipboard, { struct clipboard_data_instance *cdi = (struct clipboard_data_instance *)data; - struct gui_data *inst = cdi->inst; - if (inst->current_cdi == cdi) { + if (cdi->state && cdi->state->current_cdi == cdi) { gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, cdi->pasteout_data_utf8_len); } @@ -2339,20 +2928,26 @@ static void clipboard_clear(GtkClipboard *clipboard, gpointer data) { struct clipboard_data_instance *cdi = (struct clipboard_data_instance *)data; - struct gui_data *inst = cdi->inst; - if (inst->current_cdi == cdi) { - term_deselect(inst->term); - inst->current_cdi = NULL; + 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); } -void write_clip(void *frontend, wchar_t *data, int *attr, int len, - int must_deselect) +static void gtkwin_clip_write( + TermWin *tw, int clipboard, wchar_t *data, int *attr, + truecolour *truecolour, int len, bool must_deselect) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + struct clipboard_state *state = &inst->clipstates[clipboard]; struct clipboard_data_instance *cdi; if (inst->direct_to_font) { @@ -2364,10 +2959,17 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, return; } + if (!state->gtkclipboard) + return; + cdi = snew(struct clipboard_data_instance); - cdi->inst = inst; - inst->current_cdi = cdi; + 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; @@ -2390,9 +2992,9 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, 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(inst->clipboard, targettable, n_targets, - clipboard_provide_data, clipboard_clear, - cdi); + 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); } @@ -2401,7 +3003,9 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, static void clipboard_text_received(GtkClipboard *clipboard, const gchar *text, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; + wchar_t *paste; + int paste_len; int length; if (!text) @@ -2409,20 +3013,24 @@ static void clipboard_text_received(GtkClipboard *clipboard, length = strlen(text); - if (inst->pastein_data) - sfree(inst->pastein_data); + paste = snewn(length, wchar_t); + paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); - inst->pastein_data = snewn(length, wchar_t); - inst->pastein_data_len = mb_to_wc(CS_UTF8, 0, text, length, - inst->pastein_data, length); + term_do_paste(inst->term, paste, paste_len); - term_do_paste(inst->term); + sfree(paste); } -void request_paste(void *frontend) +static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) { - struct gui_data *inst = (struct gui_data *)frontend; - gtk_clipboard_request_text(inst->clipboard, clipboard_text_received, inst); + 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 */ @@ -2448,45 +3056,53 @@ void request_paste(void *frontend) */ /* Store the data in a cut-buffer. */ -static void store_cutbuffer(char * ptr, int len) +static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len) { #ifndef NOT_X_WINDOWS - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); - /* ICCCM says we must rotate the buffers before storing to buffer 0. */ - XRotateBuffers(disp, 1); - XStoreBytes(disp, ptr, 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); + } #endif } /* Retrieve data from a cut-buffer. * Returned data needs to be freed with XFree(). */ -static char *retrieve_cutbuffer(int *nbytes) +static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes) { #ifndef NOT_X_WINDOWS - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); - char * ptr; - ptr = XFetchBytes(disp, 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; #else + *nbytes = 0; return NULL; #endif } -void write_clip(void *frontend, wchar_t *data, int *attr, int len, - int must_deselect) +static void gtkwin_clip_write( + TermWin *tw, int clipboard, wchar_t *data, int *attr, + truecolour *truecolour, int len, bool must_deselect) { - struct gui_data *inst = (struct gui_data *)frontend; - if (inst->pasteout_data) - sfree(inst->pasteout_data); - if (inst->pasteout_data_ctext) - sfree(inst->pasteout_data_ctext); - if (inst->pasteout_data_utf8) - sfree(inst->pasteout_data_utf8); + 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 @@ -2498,127 +3114,141 @@ void write_clip(void *frontend, wchar_t *data, int *attr, int len, #ifndef NOT_X_WINDOWS XTextProperty tp; char *list[1]; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); #endif - inst->pasteout_data_utf8 = snewn(len*6, char); - inst->pasteout_data_utf8_len = len*6; - inst->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len, + 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 (inst->pasteout_data_utf8_len == 0) { - sfree(inst->pasteout_data_utf8); - inst->pasteout_data_utf8 = NULL; + if (state->pasteout_data_utf8_len == 0) { + sfree(state->pasteout_data_utf8); + state->pasteout_data_utf8 = NULL; } else { - inst->pasteout_data_utf8 = - sresize(inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len + 1, char); - inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0'; + 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] = inst->pasteout_data_utf8; - if (Xutf8TextListToTextProperty(disp, list, 1, - XCompoundTextStyle, &tp) == 0) { - inst->pasteout_data_ctext = snewn(tp.nitems+1, char); - memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems); - inst->pasteout_data_ctext_len = tp.nitems; + 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 { - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; } } else { - inst->pasteout_data_utf8 = NULL; - inst->pasteout_data_utf8_len = 0; - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; - } - - inst->pasteout_data = snewn(len*6, char); - inst->pasteout_data_len = len*6; - inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, inst->pasteout_data, - inst->pasteout_data_len, - NULL, NULL, NULL); - if (inst->pasteout_data_len == 0) { - sfree(inst->pasteout_data); - inst->pasteout_data = NULL; + 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 { - inst->pasteout_data = - sresize(inst->pasteout_data, inst->pasteout_data_len, char); + state->pasteout_data = + sresize(state->pasteout_data, state->pasteout_data_len, char); } - store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len); + /* 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); - if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY, + 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, GDK_SELECTION_PRIMARY); + gtk_selection_clear_targets(inst->area, state->atom); #endif - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_add_target(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, 1); - if (inst->pasteout_data_ctext) - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + if (state->pasteout_data_ctext) + gtk_selection_add_target(inst->area, state->atom, compound_text_atom, 1); - if (inst->pasteout_data_utf8) - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + if (state->pasteout_data_utf8) + gtk_selection_add_target(inst->area, state->atom, utf8_string_atom, 1); } if (must_deselect) - term_deselect(inst->term); + term_lost_clipboard_ownership(inst->term, clipboard); } static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, guint info, guint time_stamp, gpointer data) { - struct gui_data *inst = (struct gui_data *)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 *)inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len); + (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 *)inst->pasteout_data_ctext, - inst->pasteout_data_ctext_len); + (unsigned char *)state->pasteout_data_ctext, + state->pasteout_data_ctext_len); else gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data, - inst->pasteout_data_len); + (unsigned char *)state->pasteout_data, + state->pasteout_data_len); } static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; - - term_deselect(inst->term); - if (inst->pasteout_data) - sfree(inst->pasteout_data); - if (inst->pasteout_data_ctext) - sfree(inst->pasteout_data_ctext); - if (inst->pasteout_data_utf8) - sfree(inst->pasteout_data_utf8); - inst->pasteout_data = NULL; - inst->pasteout_data_len = 0; - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; - inst->pasteout_data_utf8 = NULL; - inst->pasteout_data_utf8_len = 0; - return TRUE; + 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; } -void request_paste(void *frontend) +static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) { - struct gui_data *inst = (struct gui_data *)frontend; + 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 @@ -2633,43 +3263,49 @@ void request_paste(void *frontend) * fails, selection_received() will be informed and will * fall back to an ordinary string. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, - utf8_string_atom, + 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, GDK_SELECTION_PRIMARY, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); + 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) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; char *text; int length; #ifndef NOT_X_WINDOWS char **list; - int free_list_required = 0; - int free_required = 0; + 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, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, compound_text_atom, inst->input_event_time); return; @@ -2680,7 +3316,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, * Failed to get UTF-8 or compound text. Try an ordinary * string. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, inst->input_event_time); return; @@ -2701,13 +3337,13 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, */ if (seldata_length <= 0) { #ifndef NOT_X_WINDOWS - text = retrieve_cutbuffer(&length); + 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 = 1; + free_required = true; #else return; #endif @@ -2719,25 +3355,25 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, #ifndef NOT_X_WINDOWS XTextProperty tp; int ret, count; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); tp.value = (unsigned char *)seldata_data; tp.encoding = (Atom) seldata_type; tp.format = gtk_selection_data_get_format(seldata); tp.nitems = seldata_length; - ret = Xutf8TextPropertyToTextList(disp, &tp, &list, &count); + 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 = 1; + free_list_required = true; } else #endif { /* * Compound text failed; fall back to STRING. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, inst->input_event_time); return; @@ -2750,16 +3386,12 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, } } - if (inst->pastein_data) - sfree(inst->pastein_data); + paste = snewn(length, wchar_t); + paste_len = mb_to_wc(charset, 0, text, length, paste, length); - inst->pastein_data = snewn(length, wchar_t); - inst->pastein_data_len = length; - inst->pastein_data_len = - mb_to_wc(charset, 0, text, length, - inst->pastein_data, inst->pastein_data_len); + term_do_paste(inst->term, paste, paste_len); - term_do_paste(inst->term); + sfree(paste); #ifndef NOT_X_WINDOWS if (free_list_required) @@ -2769,41 +3401,65 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, #endif } -void init_clipboard(struct gui_data *inst) +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. */ - unsigned char empty[] = ""; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(disp, BadMatch); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); + 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", @@ -2819,17 +3475,7 @@ void init_clipboard(struct gui_data *inst) #endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ -void get_clip(void *frontend, wchar_t ** p, int *len) -{ - struct gui_data *inst = (struct gui_data *)frontend; - - if (p) { - *p = inst->pastein_data; - *len = inst->pastein_data_len; - } -} - -static void set_window_titles(struct gui_data *inst) +static void set_window_titles(GtkFrontend *inst) { /* * We must always call set_icon_name after calling set_title, @@ -2837,30 +3483,29 @@ static void set_window_titles(struct gui_data *inst) * is life. */ gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle); - if (!conf_get_int(inst->conf, CONF_win_name_always)) + if (!conf_get_bool(inst->conf, CONF_win_name_always)) gdk_window_set_icon_name(gtk_widget_get_window(inst->window), inst->icontitle); } -void set_title(void *frontend, char *title) +static void gtkwin_set_title(TermWin *tw, const char *title) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->wintitle); inst->wintitle = dupstr(title); set_window_titles(inst); } -void set_icon(void *frontend, char *title) +static void gtkwin_set_icon_title(TermWin *tw, const char *title) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->icontitle); inst->icontitle = dupstr(title); set_window_titles(inst); } -void set_title_and_icon(void *frontend, char *title, char *icon) +void set_title_and_icon(GtkFrontend *inst, char *title, char *icon) { - struct gui_data *inst = (struct gui_data *)frontend; sfree(inst->wintitle); inst->wintitle = dupstr(title); sfree(inst->icontitle); @@ -2868,35 +3513,33 @@ void set_title_and_icon(void *frontend, char *title, char *icon) set_window_titles(inst); } -void set_sbar(void *frontend, int total, int start, int page) +static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page) { - struct gui_data *inst = (struct gui_data *)frontend; - if (!conf_get_int(inst->conf, CONF_scrollbar)) + 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); - inst->ignore_sbar = TRUE; #if !GTK_CHECK_VERSION(3,18,0) gtk_adjustment_changed(inst->sbar_adjust); #endif - inst->ignore_sbar = FALSE; + inst->ignore_sbar = false; } -void scrollbar_moved(GtkAdjustment *adj, gpointer data) +void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst) { - struct gui_data *inst = (struct gui_data *)data; - - if (!conf_get_int(inst->conf, CONF_scrollbar)) + 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(struct gui_data *inst, gboolean visible) +static void show_scrollbar(GtkFrontend *inst, gboolean visible) { inst->sbar_visible = visible; if (visible) @@ -2905,7 +3548,7 @@ static void show_scrollbar(struct gui_data *inst, gboolean visible) gtk_widget_hide(inst->sbar); } -void sys_cursor(void *frontend, int x, int y) +static void gtkwin_set_cursor_pos(TermWin *tw, int x, int y) { /* * This is meaningless under X. @@ -2918,13 +3561,14 @@ void sys_cursor(void *frontend, int x, int y) * may want to perform additional actions on any kind of bell (for * example, taskbar flashing in Windows). */ -void do_beep(void *frontend, int mode) +static void gtkwin_bell(TermWin *tw, int mode) { + /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */ if (mode == BELL_DEFAULT) - gdk_beep(); + gdk_display_beep(gdk_display_get_default()); } -int char_width(Context ctx, int uc) +static int gtkwin_char_width(TermWin *tw, int uc) { /* * In this front end, double-width characters are handled using a @@ -2933,67 +3577,63 @@ int char_width(Context ctx, int uc) return 1; } -Context get_ctx(void *frontend) +static bool gtkwin_setup_draw_ctx(TermWin *tw) { - struct gui_data *inst = (struct gui_data *)frontend; - struct draw_ctx *dctx; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); if (!gtk_widget_get_window(inst->area)) - return NULL; + return false; - dctx = snew(struct draw_ctx); - dctx->inst = inst; - dctx->uctx.type = inst->drawtype; + inst->uctx.type = inst->drawtype; #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { + if (inst->uctx.type == DRAWTYPE_GDK) { /* If we're doing GDK-based drawing, then we also expect * inst->pixmap to exist. */ - dctx->uctx.u.gdk.target = inst->pixmap; - dctx->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area)); + 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 (dctx->uctx.type == DRAWTYPE_CAIRO) { - dctx->uctx.u.cairo.widget = GTK_WIDGET(inst->area); + 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. */ - dctx->uctx.u.cairo.cr = cairo_create(inst->surface); - cairo_setup_dctx(dctx); + 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 dctx; + return true; } -void free_ctx(Context ctx) +static void gtkwin_free_draw_ctx(TermWin *tw) { - struct draw_ctx *dctx = (struct draw_ctx *)ctx; - /* struct gui_data *inst = dctx->inst; */ + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { - gdk_gc_unref(dctx->uctx.u.gdk.gc); + if (inst->uctx.type == DRAWTYPE_GDK) { + gdk_gc_unref(inst->uctx.u.gdk.gc); } #endif #ifdef DRAW_TEXT_CAIRO - if (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_destroy(dctx->uctx.u.cairo.cr); + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_destroy(inst->uctx.u.cairo.cr); } #endif - sfree(dctx); } -static void draw_update(struct draw_ctx *dctx, int x, int y, int w, int h) +static void draw_update(GtkFrontend *inst, int x, int y, int w, int h) { #if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS - if (dctx->uctx.type == DRAWTYPE_CAIRO) { + 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(dctx->inst->pixmap); - cairo_set_source_surface(cr, dctx->inst->surface, 0, 0); + 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); @@ -3009,55 +3649,108 @@ static void draw_update(struct draw_ctx *dctx, int x, int y, int w, int h) * Amazingly, this one API call is actually valid in all versions * of GTK :-) */ - gtk_widget_queue_draw_area(dctx->inst->area, x, y, w, h); + 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(struct draw_ctx *dctx, int col) +static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim) { #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { - gdk_gc_set_foreground(dctx->uctx.u.gdk.gc, &dctx->inst->cols[col]); + 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 (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb(dctx->uctx.u.cairo.cr, - dctx->inst->cols[col].red / 65535.0, - dctx->inst->cols[col].green / 65535.0, - dctx->inst->cols[col].blue / 65535.0); + 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(struct draw_ctx *dctx, int filled, +static void draw_rectangle(GtkFrontend *inst, bool filled, int x, int y, int w, int h) { #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { - gdk_draw_rectangle(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc, + 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 (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(dctx->uctx.u.cairo.cr); + if (inst->uctx.type == DRAWTYPE_CAIRO) { + cairo_new_path(inst->uctx.u.cairo.cr); if (filled) { - cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, w, h); - cairo_fill(dctx->uctx.u.cairo.cr); + cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); + cairo_fill(inst->uctx.u.cairo.cr); } else { - cairo_rectangle(dctx->uctx.u.cairo.cr, + cairo_rectangle(inst->uctx.u.cairo.cr, x + 0.5, y + 0.5, w, h); - cairo_close_path(dctx->uctx.u.cairo.cr); - cairo_stroke(dctx->uctx.u.cairo.cr); + cairo_close_path(inst->uctx.u.cairo.cr); + cairo_stroke(inst->uctx.u.cairo.cr); } } #endif } -static void draw_clip(struct draw_ctx *dctx, int x, int y, int w, int h) +static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h) { #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { + if (inst->uctx.type == DRAWTYPE_GDK) { GdkRectangle r; r.x = x; @@ -3065,59 +3758,59 @@ static void draw_clip(struct draw_ctx *dctx, int x, int y, int w, int h) r.width = w; r.height = h; - gdk_gc_set_clip_rectangle(dctx->uctx.u.gdk.gc, &r); + gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r); } #endif #ifdef DRAW_TEXT_CAIRO - if (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_reset_clip(dctx->uctx.u.cairo.cr); - cairo_new_path(dctx->uctx.u.cairo.cr); - cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, w, h); - cairo_clip(dctx->uctx.u.cairo.cr); + 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(struct draw_ctx *dctx, int x, int y) +static void draw_point(GtkFrontend *inst, int x, int y) { #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { - gdk_draw_point(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc, x, y); + 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 (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(dctx->uctx.u.cairo.cr); - cairo_rectangle(dctx->uctx.u.cairo.cr, x, y, 1, 1); - cairo_fill(dctx->uctx.u.cairo.cr); + 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(struct draw_ctx *dctx, int x0, int y0, int x1, int y1) +static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1) { #ifdef DRAW_TEXT_GDK - if (dctx->uctx.type == DRAWTYPE_GDK) { - gdk_draw_line(dctx->uctx.u.gdk.target, dctx->uctx.u.gdk.gc, + 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 (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(dctx->uctx.u.cairo.cr); - cairo_move_to(dctx->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5); - cairo_line_to(dctx->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5); - cairo_stroke(dctx->uctx.u.cairo.cr); + 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(struct draw_ctx *dctx, int x, int y, - int w, int wdouble, - int h, int hdouble, int hbothalf) +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 (dctx->uctx.type == DRAWTYPE_CAIRO) { + if (inst->uctx.type == DRAWTYPE_CAIRO) { cairo_matrix_t matrix; matrix.xy = 0; @@ -3142,18 +3835,18 @@ static void draw_stretch_before(struct draw_ctx *dctx, int x, int y, matrix.yy = 1; matrix.y0 = 0; } - cairo_transform(dctx->uctx.u.cairo.cr, &matrix); + cairo_transform(inst->uctx.u.cairo.cr, &matrix); } #endif } -static void draw_stretch_after(struct draw_ctx *dctx, int x, int y, - int w, int wdouble, - int h, int hdouble, int hbothalf) +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 (dctx->uctx.type == DRAWTYPE_GDK) { + 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. @@ -3165,9 +3858,9 @@ static void draw_stretch_after(struct draw_ctx *dctx, int x, int y, int i; if (wdouble) { for (i = 0; i < w; i++) { - gdk_draw_pixmap(dctx->uctx.u.gdk.target, - dctx->uctx.u.gdk.gc, - dctx->uctx.u.gdk.target, + 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); @@ -3183,9 +3876,9 @@ static void draw_stretch_after(struct draw_ctx *dctx, int x, int y, else dt = 1, db = 0; for (i = 0; i < h; i += 2) { - gdk_draw_pixmap(dctx->uctx.u.gdk.target, - dctx->uctx.u.gdk.gc, - dctx->uctx.u.gdk.target, + 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); @@ -3197,22 +3890,26 @@ static void draw_stretch_after(struct draw_ctx *dctx, int x, int y, #endif #endif /* DRAW_TEXT_GDK */ #ifdef DRAW_TEXT_CAIRO - if (dctx->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_matrix(dctx->uctx.u.cairo.cr, - &dctx->uctx.u.cairo.origmatrix); + 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(struct gui_data *inst) +static void draw_backing_rect(GtkFrontend *inst) { - struct draw_ctx *dctx = get_ctx(inst); - int w = inst->width * inst->font_width + 2*inst->window_border; - int h = inst->height * inst->font_height + 2*inst->window_border; - draw_set_colour(dctx, 258); - draw_rectangle(dctx, 1, 0, 0, w, h); - draw_update(dctx, 0, 0, w, h); - free_ctx(dctx); + 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); } /* @@ -3221,14 +3918,14 @@ static void draw_backing_rect(struct gui_data *inst) * * We are allowed to fiddle with the contents of `text'. */ -void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) +static void do_text_internal( + GtkFrontend *inst, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) { - struct draw_ctx *dctx = (struct draw_ctx *)ctx; - struct gui_data *inst = dctx->inst; int ncombining; - int nfg, nbg, t, fontid, shadow, rlen, widefactor, bold; - int monochrome = + 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) { @@ -3237,12 +3934,21 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } 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; @@ -3253,11 +3959,13 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, 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 = shadow = 0; + fontid = 0; if (attr & ATTR_WIDE) { widefactor = 2; @@ -3267,10 +3975,10 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) { - bold = 1; + bold = true; fontid |= 1; } else { - bold = 0; + bold = false; } if (!inst->fonts[fontid]) { @@ -3300,39 +4008,45 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } else rlen = len; - draw_clip(dctx, + 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(dctx, + draw_stretch_before(inst, x*inst->font_width+inst->window_border, y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, TRUE, + rlen*widefactor*inst->font_width, true, inst->font_height, ((lattr & LATTR_MODE) != LATTR_WIDE), ((lattr & LATTR_MODE) == LATTR_BOT)); } - draw_set_colour(dctx, nbg); - draw_rectangle(dctx, TRUE, + 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); - draw_set_colour(dctx, nfg); + 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(&dctx->uctx, inst->fonts[fontid], + 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(&dctx->uctx, inst->fonts[fontid], + 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), @@ -3344,31 +4058,31 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, int uheight = inst->fonts[0]->ascent + 1; if (uheight >= inst->font_height) uheight = inst->font_height - 1; - draw_line(dctx, x*inst->font_width+inst->window_border, + 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 ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_after(dctx, + draw_stretch_after(inst, x*inst->font_width+inst->window_border, y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, TRUE, + rlen*widefactor*inst->font_width, true, inst->font_height, ((lattr & LATTR_MODE) != LATTR_WIDE), ((lattr & LATTR_MODE) == LATTR_BOT)); } } -void do_text(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) +static void gtkwin_draw_text( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) { - struct draw_ctx *dctx = (struct draw_ctx *)ctx; - struct gui_data *inst = dctx->inst; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); int widefactor; - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); if (attr & ATTR_WIDE) { widefactor = 2; @@ -3385,31 +4099,31 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, len *= 2; } - draw_update(dctx, + 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); } -void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) +static void gtkwin_draw_cursor( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) { - struct draw_ctx *dctx = (struct draw_ctx *)ctx; - struct gui_data *inst = dctx->inst; - - int active, passive, widefactor; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); + bool active, passive; + int widefactor; if (attr & TATTR_PASCURS) { attr &= ~TATTR_PASCURS; - passive = 1; + passive = true; } else - passive = 0; + passive = false; if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) { attr &= ~TATTR_ACTCURS; - active = 1; + active = true; } else - active = 0; - do_text_internal(ctx, x, y, text, len, attr, lattr); + active = false; + do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); if (attr & TATTR_COMBINING) len = 1; @@ -3436,8 +4150,8 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, * if it's passive. */ if (passive) { - draw_set_colour(dctx, 261); - draw_rectangle(dctx, FALSE, + 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, @@ -3475,22 +4189,22 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, length = inst->font_height; } - draw_set_colour(dctx, 261); + draw_set_colour(inst, 261, false); if (passive) { for (i = 0; i < length; i++) { if (i % 2 == 0) { - draw_point(dctx, startx, starty); + draw_point(inst, startx, starty); } startx += dx; starty += dy; } } else if (active) { - draw_line(dctx, startx, starty, + draw_line(inst, startx, starty, startx + (length-1) * dx, starty + (length-1) * dy); } /* else no cursor (e.g., blinked off) */ } - draw_update(dctx, + 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); @@ -3507,7 +4221,7 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, #endif } -GdkCursor *make_mouse_ptr(struct gui_data *inst, int cursor_val) +GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val) { if (cursor_val == -1) { #if GTK_CHECK_VERSION(2,16,0) @@ -3544,46 +4258,39 @@ void modalfatalbox(const char *p, ...) exit(1); } -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); -} - -const char *get_x_display(void *frontend) +static const char *gtk_seat_get_x_display(Seat *seat) { return gdk_get_display(); } #ifndef NOT_X_WINDOWS -long get_windowid(void *frontend) +static bool gtk_seat_get_windowid(Seat *seat, long *id) { - struct gui_data *inst = (struct gui_data *)frontend; - return (long)GDK_WINDOW_XID(gtk_widget_get_window(inst->area)); + 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 -int frontend_is_utf8(void *frontend) +static bool gtkwin_is_utf8(TermWin *tw) { - struct gui_data *inst = (struct gui_data *)frontend; + GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); return inst->ucsdata.line_codepage == CS_UTF8; } -char *setup_fonts_ucs(struct gui_data *inst) +char *setup_fonts_ucs(GtkFrontend *inst) { - int shadowbold = conf_get_int(inst->conf, CONF_shadowbold); + 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, + fonts[0] = multifont_create(inst->area, fs->name, false, false, shadowboldoffset, shadowbold); if (!fonts[0]) { return dupprintf("unable to load font \"%s\"", fs->name); @@ -3593,7 +4300,7 @@ char *setup_fonts_ucs(struct gui_data *inst) if (shadowbold || !fs->name[0]) { fonts[1] = NULL; } else { - fonts[1] = multifont_create(inst->area, fs->name, FALSE, TRUE, + fonts[1] = multifont_create(inst->area, fs->name, false, true, shadowboldoffset, shadowbold); if (!fonts[1]) { if (fonts[0]) @@ -3604,7 +4311,7 @@ char *setup_fonts_ucs(struct gui_data *inst) fs = conf_get_fontspec(inst->conf, CONF_widefont); if (fs->name[0]) { - fonts[2] = multifont_create(inst->area, fs->name, TRUE, FALSE, + fonts[2] = multifont_create(inst->area, fs->name, true, false, shadowboldoffset, shadowbold); if (!fonts[2]) { for (i = 0; i < 2; i++) @@ -3620,7 +4327,7 @@ char *setup_fonts_ucs(struct gui_data *inst) if (shadowbold || !fs->name[0]) { fonts[3] = NULL; } else { - fonts[3] = multifont_create(inst->area, fs->name, TRUE, TRUE, + fonts[3] = multifont_create(inst->area, fs->name, true, true, shadowboldoffset, shadowbold); if (!fonts[3]) { for (i = 0; i < 3; i++) @@ -3641,12 +4348,22 @@ char *setup_fonts_ucs(struct gui_data *inst) inst->fonts[i] = fonts[i]; } - inst->font_width = inst->fonts[0]->width; - inst->font_height = inst->fonts[0]->height; + 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_int(inst->conf, CONF_utf8_override), + conf_get_bool(inst->conf, CONF_utf8_override), inst->fonts[0]->public_charset, conf_get_int(inst->conf, CONF_vtmode)); @@ -3667,7 +4384,7 @@ static void find_app_menu_bar(GtkWidget *widget, gpointer data) } #endif -static void compute_geom_hints(struct gui_data *inst, GdkGeometry *geom) +static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) { /* * Unused fields in geom. @@ -3768,7 +4485,7 @@ static void compute_geom_hints(struct gui_data *inst, GdkGeometry *geom) #endif } -void set_geom_hints(struct gui_data *inst) +void set_geom_hints(GtkFrontend *inst) { GdkGeometry geom; gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC; @@ -3782,7 +4499,7 @@ void set_geom_hints(struct gui_data *inst) } #if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(struct gui_data *inst, +static void compute_whole_window_size(GtkFrontend *inst, int wchars, int hchars, int *wpix, int *hpix) { @@ -3795,47 +4512,153 @@ static void compute_whole_window_size(struct gui_data *inst, void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; term_clrsb(inst->term); } void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; - term_pwron(inst->term, TRUE); + 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) { - struct gui_data *inst = (struct gui_data *)data; - term_copyall(inst->term); + GtkFrontend *inst = (GtkFrontend *)data; + static const int clips[] = { COPYALL_CLIPBOARDS }; + term_copyall(inst->term, clips, lenof(clips)); } void special_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; - int code = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), - "user-data")); + GtkFrontend *inst = (GtkFrontend *)data; + SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data"); - if (inst->back) - inst->back->special(inst->backhandle, code); + if (inst->backend) + backend_special(inst->backend, sc->code, sc->arg); } void about_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; about_box(inst->window); } void event_log_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)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", NULL); + + ctx = snew(struct after_change_settings_dialog_ctx); + ctx->inst = inst; + ctx->newconf = conf_copy(inst->conf); + + 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) { /* This maps colour indices in inst->conf to those used in inst->cols. */ static const int ww[] = { @@ -3843,25 +4666,27 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; - struct gui_data *inst = (struct gui_data *)data; - char *title; - Conf *oldconf, *newconf; - int i, j, need_size; + 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; + int i, j; + bool need_size; + + sfree(vctx); /* we've copied this already */ + + if (retval < 0) { + /* If the dialog box was aborted without giving a result + * (probably because the whole session window closed), we have + * nothing further to do. */ + return; + } assert(lenof(ww) == NCFGCOLOURS); - if (inst->reconfiguring) - return; - else - inst->reconfiguring = TRUE; - - title = dupcat(appname, " Reconfiguration", NULL); - - oldconf = inst->conf; - newconf = conf_copy(inst->conf); + unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE); - if (do_config_box(title, newconf, 1, - inst->back?inst->back->cfg_info(inst->backhandle):0)) { + if (retval) { inst->conf = newconf; /* Pass new config data to the logging module */ @@ -3876,9 +4701,10 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) } /* 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->back) - inst->back->reconfig(inst->backhandle, inst->conf); + if (inst->backend) + backend_reconfig(inst->backend, inst->conf); cache_conf_values(inst); @@ -3913,21 +4739,21 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) } } - need_size = FALSE; + 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_int(oldconf, CONF_scrollbar) != - conf_get_int(newconf, CONF_scrollbar)) { - show_scrollbar(inst, conf_get_int(newconf, CONF_scrollbar)); - need_size = TRUE; + 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_int(oldconf, CONF_scrollbar_on_left) != - conf_get_int(newconf, CONF_scrollbar_on_left)) { + 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_int(newconf, CONF_scrollbar_on_left) + conf_get_bool(newconf, CONF_scrollbar_on_left) ? 0 : 1); } @@ -3936,7 +4762,8 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) */ if (strcmp(conf_get_str(oldconf, CONF_wintitle), conf_get_str(newconf, CONF_wintitle))) - set_title(inst, conf_get_str(newconf, CONF_wintitle)); + win_set_title(&inst->termwin, + conf_get_str(newconf, CONF_wintitle)); set_window_titles(inst); /* @@ -3953,12 +4780,12 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) conf_get_fontspec(newconf, CONF_wideboldfont)->name) || strcmp(conf_get_str(oldconf, CONF_line_codepage), conf_get_str(newconf, CONF_line_codepage)) || - conf_get_int(oldconf, CONF_utf8_override) != - conf_get_int(newconf, CONF_utf8_override) || + conf_get_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_int(oldconf, CONF_shadowbold) != - conf_get_int(newconf, CONF_shadowbold) || + 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); @@ -3966,14 +4793,14 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) char *msgboxtext = dupprintf("Could not change fonts in terminal window: %s\n", errmsg); - messagebox(inst->window, "Font setup error", msgboxtext, - string_width("Could not change fonts in terminal window:"), - FALSE, "OK", 'o', +1, 1, - NULL); + 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; + need_size = true; } } @@ -3988,8 +4815,9 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) conf_get_int(newconf, CONF_window_border) || need_size) { set_geom_hints(inst); - request_resize(inst, conf_get_int(newconf, CONF_width), - conf_get_int(newconf, CONF_height)); + 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 @@ -4016,11 +4844,9 @@ void change_settings_menuitem(GtkMenuItem *item, gpointer data) } else { conf_free(newconf); } - sfree(title); - inst->reconfiguring = FALSE; } -static void change_font_size(struct gui_data *inst, int increment) +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, @@ -4064,8 +4890,8 @@ static void change_font_size(struct gui_data *inst, int increment) } set_geom_hints(inst); - request_resize(inst, conf_get_int(inst->conf, CONF_width), - conf_get_int(inst->conf, CONF_height)); + 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); @@ -4083,7 +4909,7 @@ static void change_font_size(struct gui_data *inst, int increment) void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) { - struct gui_data *inst = (struct gui_data *)gdata; + GtkFrontend *inst = (GtkFrontend *)gdata; launch_duplicate_session(inst->conf); } @@ -4095,13 +4921,13 @@ void new_session_menuitem(GtkMenuItem *item, gpointer data) void restart_session_menuitem(GtkMenuItem *item, gpointer data) { - struct gui_data *inst = (struct gui_data *)data; + GtkFrontend *inst = (GtkFrontend *)data; - if (!inst->back) { - logevent(inst, "----- Session restarted -----"); - term_pwron(inst->term, FALSE); + if (!inst->backend) { + logevent(inst->logctx, "----- Session restarted -----"); + term_pwron(inst->term, false); start_backend(inst); - inst->exited = FALSE; + inst->exited = false; } } @@ -4119,16 +4945,50 @@ void saved_session_freedata(GtkMenuItem *item, gpointer 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) { - struct gui_data *inst = (struct gui_data *)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); + get_sesslist(&sesslist, true); /* skip sesslist.sessions[0] == Default Settings */ for (i = 1; i < sesslist.nsessions; i++) { GtkWidget *menuitem = @@ -4147,11 +5007,11 @@ static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) if (sesslist.nsessions <= 1) { GtkWidget *menuitem = gtk_menu_item_new_with_label("(No sessions)"); - gtk_widget_set_sensitive(menuitem, FALSE); + gtk_widget_set_sensitive(menuitem, false); gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem); gtk_widget_show(menuitem); } - get_sesslist(&sesslist, FALSE); /* free up */ + get_sesslist(&sesslist, false); /* free up */ } void set_window_icon(GtkWidget *window, const char *const *const *icon, @@ -4190,14 +5050,15 @@ void set_window_icon(GtkWidget *window, const char *const *const *icon, #endif } -void update_specials_menu(void *frontend) -{ - struct gui_data *inst = (struct gui_data *)frontend; +static void free_special_cmd(gpointer data) { sfree(data); } - const struct telnet_special *specials; +static void gtk_seat_update_specials_menu(Seat *seat) +{ + GtkFrontend *inst = container_of(seat, GtkFrontend, seat); + const SessionSpecial *specials; - if (inst->back) - specials = inst->back->get_specials(inst->backhandle); + if (inst->backend) + specials = backend_get_specials(inst->backend); else specials = NULL; @@ -4213,7 +5074,7 @@ void update_specials_menu(void *frontend) for (i = 0; nesting > 0; i++) { GtkWidget *menuitem = NULL; switch (specials[i].code) { - case TS_SUBMENU: + case SS_SUBMENU: assert (nesting < 2); saved_menu = menu; /* XXX lame stacking */ menu = gtk_menu_new(); @@ -4224,20 +5085,24 @@ void update_specials_menu(void *frontend) menuitem = NULL; nesting++; break; - case TS_EXITMENU: + case SS_EXITMENU: nesting--; if (nesting) { menu = saved_menu; /* XXX lame stacking */ saved_menu = NULL; } break; - case TS_SEP: + case SS_SEP: menuitem = gtk_menu_item_new(); break; default: menuitem = gtk_menu_item_new_with_label(specials[i].name); - g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER(specials[i].code)); + { + 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; @@ -4255,30 +5120,30 @@ void update_specials_menu(void *frontend) } } -static void start_backend(struct gui_data *inst) +static void start_backend(GtkFrontend *inst) { - extern Backend *select_backend(Conf *conf); + const struct BackendVtable *vt; char *realhost; const char *error; char *s; - inst->back = select_backend(inst->conf); + vt = select_backend(inst->conf); - error = inst->back->init((void *)inst, &inst->backhandle, - inst->conf, - conf_get_str(inst->conf, CONF_host), - conf_get_int(inst->conf, CONF_port), - &realhost, - conf_get_int(inst->conf, CONF_tcp_nodelay), - conf_get_int(inst->conf, CONF_tcp_keepalives)); + 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) { char *msg = dupprintf("Unable to open connection to %s:\n%s", - conf_get_str(inst->conf, CONF_host), error); - inst->exited = TRUE; - fatal_message_box(inst->window, msg); + conf_dest(inst->conf), error); + inst->exited = true; + seat_connection_fatal(&inst->seat, msg); sfree(msg); - exit(0); + return; } s = conf_get_str(inst->conf, CONF_wintitle); @@ -4291,17 +5156,15 @@ static void start_backend(struct gui_data *inst) } sfree(realhost); - inst->back->provide_logctx(inst->backhandle, inst->logctx); + term_provide_backend(inst->term, inst->backend); - term_provide_resize_fn(inst->term, inst->back->size, inst->backhandle); + inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend, + &inst->seat); - inst->ldisc = - ldisc_create(inst->conf, inst->term, inst->back, inst->backhandle, - inst); - - gtk_widget_set_sensitive(inst->restartitem, FALSE); + 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) @@ -4325,16 +5188,52 @@ static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) geometry->height = gdk_screen_height(); #endif } +#endif + +static const TermWinVtable gtk_termwin_vt = { + gtkwin_setup_draw_ctx, + gtkwin_draw_text, + gtkwin_draw_cursor, + gtkwin_char_width, + gtkwin_free_draw_ctx, + gtkwin_set_cursor_pos, + gtkwin_set_raw_mouse_mode, + gtkwin_set_scrollbar, + gtkwin_bell, + gtkwin_clip_write, + gtkwin_clip_request_paste, + gtkwin_refresh, + gtkwin_request_resize, + gtkwin_set_title, + gtkwin_set_icon_title, + gtkwin_set_minimised, + gtkwin_is_minimised, + gtkwin_set_maximised, + gtkwin_move, + gtkwin_set_zorder, + gtkwin_palette_get, + gtkwin_palette_set, + gtkwin_palette_reset, + gtkwin_get_pos, + gtkwin_get_pixels, + gtkwin_get_title, + gtkwin_is_utf8, +}; -struct gui_data *new_session_window(Conf *conf, const char *geometry_string) +void new_session_window(Conf *conf, const char *geometry_string) { - struct gui_data *inst; + GtkFrontend *inst; + + prepare_session(conf); /* * Create an instance structure and initialise to zeroes */ - inst = snew(struct gui_data); + 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; @@ -4343,8 +5242,14 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) #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; @@ -4357,7 +5262,7 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) if (flags & (XValue | YValue)) { inst->xpos = x; inst->ypos = y; - inst->gotpos = TRUE; + inst->gotpos = true; inst->gravity = ((flags & XNegative ? 1 : 0) | (flags & YNegative ? 2 : 0)); } @@ -4365,24 +5270,30 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) #endif if (!compound_text_atom) - compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE); + compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", false); if (!utf8_string_atom) - utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE); + 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"); -#if GTK_CHECK_VERSION(2,0,0) - inst->imc = gtk_im_multicontext_new(); -#endif - { char *errmsg = setup_fonts_ucs(inst); if (errmsg) { - fprintf(stderr, "%s: %s\n", appname, errmsg); - exit(1); + 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"); { @@ -4393,13 +5304,11 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) GdkWindow *gdkwin; gtk_widget_realize(GTK_WIDGET(inst->window)); gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window)); - if (gdk_window_ensure_native(gdkwin)) { - Display *disp = - GDK_DISPLAY_XDISPLAY(gdk_window_get_display(gdkwin)); + if (inst->disp && gdk_window_ensure_native(gdkwin)) { XClassHint *xch = XAllocClassHint(); xch->res_name = (char *)winclass; xch->res_class = (char *)winclass; - XSetClassHint(disp, GDK_WINDOW_XID(gdkwin), xch); + XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch); XFree(xch); } #endif @@ -4420,7 +5329,7 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) /* * Set up the colour map. */ - palette_reset(inst); + win_palette_reset(&inst->termwin); inst->width = conf_get_int(inst->conf, CONF_width); inst->height = conf_get_int(inst->conf, CONF_height); @@ -4430,22 +5339,22 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) 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)); + 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_int(inst->conf, CONF_scrollbar_on_left)) - gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); - gtk_box_pack_start(inst->hbox, inst->area, TRUE, TRUE, 0); - if (!conf_get_int(inst->conf, CONF_scrollbar_on_left)) - gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0); + 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_int(inst->conf, CONF_scrollbar)); + show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar)); gtk_widget_show(GTK_WIDGET(inst->hbox)); /* @@ -4518,8 +5427,14 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) 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); +#if GTK_CHECK_VERSION(3,10,0) g_signal_connect(G_OBJECT(inst->area), "configure_event", - G_CALLBACK(configure_area), inst); + 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); @@ -4541,7 +5456,7 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) g_signal_connect(G_OBJECT(inst->imc), "commit", G_CALLBACK(input_method_commit_event), inst); #endif - if (conf_get_int(inst->conf, CONF_scrollbar)) + 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), @@ -4553,11 +5468,7 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) #endif ); - { - extern const char *const *const main_icon[]; - extern const int n_main_icon; - set_window_icon(inst->window, main_icon, n_main_icon); - } + set_window_icon(inst->window, main_icon, n_main_icon); gtk_widget_show(inst->window); @@ -4569,7 +5480,6 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) { GtkWidget *menuitem; char *s; - extern const int use_event_log, new_session, saved_sessions; inst->menu = gtk_menu_new(); @@ -4600,7 +5510,7 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) MKMENUITEM("New Session...", new_session_menuitem); MKMENUITEM("Restart Session", restart_session_menuitem); inst->restartitem = menuitem; - gtk_widget_set_sensitive(inst->restartitem, FALSE); + gtk_widget_set_sensitive(inst->restartitem, false); MKMENUITEM("Duplicate Session", dup_session_menuitem); if (saved_sessions) { inst->sessionsmenu = gtk_menu_new(); @@ -4625,6 +5535,11 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) 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, NULL); @@ -4640,22 +5555,22 @@ struct gui_data *new_session_window(Conf *conf, const char *geometry_string) inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH); inst->blankcursor = make_mouse_ptr(inst, -1); inst->currcursor = inst->textcursor; - show_mouseptr(inst, 1); + show_mouseptr(inst, true); inst->eventlogstuff = eventlogstuff_new(); - inst->term = term_init(inst->conf, &inst->ucsdata, inst); - inst->logctx = log_init(inst, inst->conf); + 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)); - start_backend(inst); - - ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ + inst->exited = false; - inst->exited = FALSE; + start_backend(inst); - return inst; + if (inst->ldisc) /* early backend failure might make this NULL already */ + ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ } diff --git a/unix/osxlaunch.c b/unix/osxlaunch.c index 2627c642..d560df93 100644 --- a/unix/osxlaunch.c +++ b/unix/osxlaunch.c @@ -10,8 +10,8 @@ * * But the GTK program won't start up unless all those shared * libraries etc are already pointed to by environment variables like - * DYLD_LIBRARY_PATH, which won't be set up when the bundle is - * launched. + * GTK_PATH and PANGO_LIBDIR and things like that, which won't be set + * up when the bundle is launched. * * Hence, gtk-mac-bundler expects to install the program in the bundle * under a name like 'Contents/MacOS/Program-bin'; and the file called @@ -48,7 +48,7 @@ #include #include -#ifndef __APPLE__ +#if !defined __APPLE__ && !defined TEST_COMPILE_ON_LINUX /* When we're not compiling for OS X, it's easier to just turn this * program into a trivial hello-world by ifdef in the source than it * is to remove it in the makefile edifice. */ @@ -61,7 +61,24 @@ int main(int argc, char **argv) #include #include + +#ifdef __APPLE__ #include +#else +/* For Linux, a bodge to let as much of this code still run as + * possible, so that you can run it under friendly debugging tools + * like valgrind. */ +int _NSGetExecutablePath(char *out, uint32_t *outlen) +{ + static const char toret[] = "/proc/self/exe"; + if (out != NULL && *outlen < sizeof(toret)) + return -1; + *outlen = sizeof(toret); + if (out) + memcpy(out, toret, sizeof(toret)); + return 0; +} +#endif /* ---------------------------------------------------------------------- * Find an alphabetic prefix unused by any environment variable name. @@ -130,11 +147,13 @@ char *get_unused_env_prefix(void) char **e; qhead = (struct bucket *)malloc(sizeof(struct bucket)); - qhead->prefixlen = 0; if (!qhead) { fprintf(stderr, "out of memory\n"); exit(1); } + qhead->prefixlen = 0; + qhead->first_node = NULL; + qhead->next_bucket = NULL; for (e = environ; *e; e++) qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "=")); @@ -151,6 +170,7 @@ char *get_unused_env_prefix(void) exit(1); } buckets[i]->prefixlen = qhead->prefixlen + 1; + buckets[i]->first_node = NULL; qtail->next_bucket = buckets[i]; qtail = buckets[i]; } @@ -335,19 +355,35 @@ char *alloc_cat(const char *str1, const char *str2) * Overwrite an environment variable, preserving the old one for the * real app to restore. */ +void setenv_wrap(const char *name, const char *value) +{ +#ifdef DEBUG_OSXLAUNCH + printf("setenv(\"%s\",\"%s\")\n", name, value); +#endif + setenv(name, value, 1); +} + +void unsetenv_wrap(const char *name) +{ +#ifdef DEBUG_OSXLAUNCH + printf("unsetenv(\"%s\")\n", name); +#endif + unsetenv(name); +} + char *prefix, *prefixset, *prefixunset; void overwrite_env(const char *name, const char *value) { const char *oldvalue = getenv(name); if (oldvalue) { - setenv(alloc_cat(prefixset, name), oldvalue, 1); + setenv_wrap(alloc_cat(prefixset, name), oldvalue); } else { - setenv(alloc_cat(prefixunset, name), "", 1); + setenv_wrap(alloc_cat(prefixunset, name), ""); } if (value) - setenv(name, value, 1); + setenv_wrap(name, value); else - unsetenv(name); + unsetenv_wrap(name); } /* ---------------------------------------------------------------------- @@ -360,6 +396,11 @@ int main(int argc, char **argv) prefixset = alloc_cat(prefix, "s"); prefixunset = alloc_cat(prefix, "u"); +#ifdef DEBUG_OSXLAUNCH + printf("Environment prefixes: main=\"%s\", set=\"%s\", unset=\"%s\"\n", + prefix, prefixset, prefixunset); +#endif + char *prog_path = get_program_path(); // /Contents/MacOS/ char *macos = dirname_wrapper(prog_path); // /Contents/MacOS char *contents = dirname_wrapper(macos); // /Contents @@ -374,7 +415,7 @@ int main(int argc, char **argv) char *locale = alloc_cat(share, "/locale"); char *realbin = alloc_cat(prog_path, "-bin"); - overwrite_env("DYLD_LIBRARY_PATH", lib); +// overwrite_env("DYLD_LIBRARY_PATH", lib); overwrite_env("XDG_CONFIG_DIRS", xdg); overwrite_env("XDG_DATA_DIRS", share); overwrite_env("GTK_DATA_PREFIX", resources); @@ -395,19 +436,35 @@ int main(int argc, char **argv) } int j = 0; new_argv[j++] = realbin; +#ifdef DEBUG_OSXLAUNCH + printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); +#endif { int i = 1; if (i < argc && !strncmp(argv[i], "-psn_", 5)) i++; - for (; i < argc; i++) + for (; i < argc; i++) { new_argv[j++] = argv[i]; +#ifdef DEBUG_OSXLAUNCH + printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); +#endif + } } new_argv[j++] = prefix; +#ifdef DEBUG_OSXLAUNCH + printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); +#endif new_argv[j++] = NULL; +#ifdef DEBUG_OSXLAUNCH + printf("executing \"%s\"\n", realbin); +#endif execv(realbin, new_argv); perror("execv"); + free(new_argv); + free(contents); + free(macos); return 127; } diff --git a/unix/pterm.bundle b/unix/pterm.bundle index 377fee0d..0d701216 100644 --- a/unix/pterm.bundle +++ b/unix/pterm.bundle @@ -2,7 +2,11 @@ - ${env:JHBUILD_PREFIX} + + ${env:PUTTY_GTK_PREFIX_FROM_MAKEFILE} + gtk+-3.0 + ${env:PUTTY_GTK_PREFIX_FROM_MAKEFILE} + gtk+-3.0 - + + + + + + + + + + + + + + + + + @@ -22,11 +38,8 @@ - - - - - + + @@ -42,6 +55,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -63,7 +110,7 @@ Buildscr. --> + Source="../doc/putty.chm" KeyPath="yes"> @@ -209,7 +256,7 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx + Source="../LICENCE" KeyPath="yes" /> @@ -452,8 +499,10 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx + 1 "1"]]> + 1 @@ -462,9 +511,13 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx 1 1 + NOT WIXUI_DONTVALIDATEPATH "1"]]> WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" + + 1 + 1 1 @@ -482,8 +535,10 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx 1 1 + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + @@ -518,6 +574,7 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx + + + + A network client and terminal emulator + + + + + + + + + + true + + + diff --git a/windows/puttytel.rc b/windows/puttytel.rc index dbdfc46d..a5cba2c8 100644 --- a/windows/puttytel.rc +++ b/windows/puttytel.rc @@ -6,6 +6,5 @@ #include "win_res.rc2" #ifndef NO_MANIFESTS -/* FIXME */ -1 RT_MANIFEST "putty.mft" +1 RT_MANIFEST "puttytel.mft" #endif /* NO_MANIFESTS */ diff --git a/windows/rcstuff.h b/windows/rcstuff.h index 22b22035..ee2c7696 100644 --- a/windows/rcstuff.h +++ b/windows/rcstuff.h @@ -9,7 +9,7 @@ #include #else -/* Some compilers, like Borland, don't have winresrc.h */ +/* Some compilers don't have winresrc.h */ #ifndef NO_WINRESRC_H #ifndef MSVC4 #include diff --git a/windows/sizetip.c b/windows/sizetip.c index 4f0a195f..e91c5b90 100644 --- a/windows/sizetip.c +++ b/windows/sizetip.c @@ -20,7 +20,7 @@ static LRESULT CALLBACK SizeTipWndProc(HWND hWnd, UINT nMsg, switch (nMsg) { case WM_ERASEBKGND: - return TRUE; + return true; case WM_PAINT: { @@ -80,7 +80,7 @@ static LRESULT CALLBACK SizeTipWndProc(HWND hWnd, UINT nMsg, SetWindowPos(hWnd, NULL, 0, 0, sz.cx + 6, sz.cy + 6, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); - InvalidateRect(hWnd, NULL, FALSE); + InvalidateRect(hWnd, NULL, false); DeleteDC(hdc); } @@ -91,7 +91,7 @@ static LRESULT CALLBACK SizeTipWndProc(HWND hWnd, UINT nMsg, } static HWND tip_wnd = NULL; -static int tip_enabled = 0; +static bool tip_enabled = false; void UpdateSizeTip(HWND src, int cx, int cy) { @@ -183,7 +183,7 @@ void UpdateSizeTip(HWND src, int cx, int cy) } } -void EnableSizeTip(int bEnable) +void EnableSizeTip(bool bEnable) { if (tip_wnd && !bEnable) { DestroyWindow(tip_wnd); diff --git a/windows/version.rc2 b/windows/version.rc2 index 500f9002..5cc127a4 100644 --- a/windows/version.rc2 +++ b/windows/version.rc2 @@ -1,9 +1,12 @@ /* * Standard Windows version information. * (For inclusion in other .rc files with appropriate macro definitions.) - * FIXME: This file is called '.rc2' rather than '.rc' to avoid MSVC trying - * to compile it on its own when using the project files. Nicer solutions - * welcome. + * + * 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 "version.h" diff --git a/windows/website.url b/windows/website.url index 4b50369c..4f6d47d1 100644 Binary files a/windows/website.url and b/windows/website.url differ diff --git a/windows/win_res.rc2 b/windows/win_res.rc2 index 92d39cd5..503fa022 100644 --- a/windows/win_res.rc2 +++ b/windows/win_res.rc2 @@ -1,12 +1,16 @@ /* * Windows resources shared between PuTTY and PuTTYtel, to be #include'd * after defining appropriate macros. + * * Note that many of these strings mention PuTTY. Due to restrictions in * VC's handling of string concatenation, this can't easily be fixed. * It's fixed up at runtime. - * FIXME: This file is called '.rc2' rather than '.rc' to avoid MSVC trying - * to compile it on its own when using the project files. Nicer solutions - * welcome. + * + * 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" @@ -16,15 +20,15 @@ IDI_MAINICON ICON "putty.ico" IDI_CFGICON ICON "puttycfg.ico" /* Accelerators used: clw */ -IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 106 +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, 88, 48, 14 - PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 88, 70, 14 - PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 88, 70, 14 - EDITTEXT IDA_TEXT, 10, 6, 250, 80, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE + 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 */ diff --git a/windows/wincapi.c b/windows/wincapi.c index 2550b6de..5ff7cdfa 100644 --- a/windows/wincapi.c +++ b/windows/wincapi.c @@ -9,17 +9,24 @@ #define WINCAPI_GLOBAL #include "wincapi.h" -int got_crypt(void) +bool got_crypt(void) { - static int attempted = FALSE; - static int successful; + static bool attempted = false; + static bool successful; static HMODULE crypt; if (!attempted) { - attempted = TRUE; + attempted = true; crypt = load_system32_dll("crypt32.dll"); successful = crypt && - GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); +#ifdef COVERITY + /* The build toolchain I use with Coverity doesn't know + * about this function, so can't type-check it */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(crypt, CryptProtectMemory) +#else + GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory) +#endif + ; } return successful; } diff --git a/windows/wincapi.h b/windows/wincapi.h index 06ee2d36..f327be27 100644 --- a/windows/wincapi.h +++ b/windows/wincapi.h @@ -13,6 +13,6 @@ DECL_WINDOWS_FUNCTION(WINCAPI_GLOBAL, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD)); -int got_crypt(void); +bool got_crypt(void); #endif diff --git a/windows/wincfg.c b/windows/wincfg.c index 9d3673a6..493e0006 100644 --- a/windows/wincfg.c +++ b/windows/wincfg.c @@ -10,7 +10,7 @@ #include "dialog.h" #include "storage.h" -static void about_handler(union control *ctrl, void *dlg, +static void about_handler(union control *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, void *dlg, } } -static void help_handler(union control *ctrl, void *dlg, +static void help_handler(union control *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, void *dlg, } } -static void variable_pitch_handler(union control *ctrl, void *dlg, +static void variable_pitch_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { if (event == EVENT_REFRESH) { @@ -40,8 +40,8 @@ static void variable_pitch_handler(union control *ctrl, void *dlg, } } -void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, - int midsession, int protocol) +void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, + bool midsession, int protocol) { struct controlset *s; union control *c; @@ -157,7 +157,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, } } ctrl_filesel(s, "Custom sound file to play as a bell:", NO_SHORTCUT, - FILTER_WAVE_FILES, FALSE, "Select bell sound file", + FILTER_WAVE_FILES, false, "Select bell sound file", HELPCTX(bell_style), conf_filesel_handler, I(CONF_bell_wavefile)); @@ -268,10 +268,10 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, /* * RTF paste is Windows-specific. */ - s = ctrl_getset(b, "Window/Selection", "format", - "Formatting of pasted characters"); - ctrl_checkbox(s, "Paste to clipboard in RTF as well as plain text", 'f', - HELPCTX(selection_rtf), + 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)); /* @@ -393,10 +393,10 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, * $XAUTHORITY is not reliable on Windows, so we provide a * means to override it. */ - if (!midsession && backend_from_proto(PROT_SSH)) { + 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", + NULL, false, "Select X authority file", HELPCTX(ssh_tunnels_xauthority), conf_filesel_handler, I(CONF_xauthfile)); } diff --git a/windows/wincons.c b/windows/wincons.c index efdecd58..48dd3c2e 100644 --- a/windows/wincons.c +++ b/windows/wincons.c @@ -11,9 +11,7 @@ #include "storage.h" #include "ssh.h" -int console_batch_mode = FALSE; - -static void *console_logctx = NULL; +bool console_batch_mode = false; /* * Clean up and exit. @@ -26,28 +24,69 @@ void cleanup_exit(int code) sk_cleanup(); random_save_seed(); -#ifdef MSCRYPTOAPI - crypto_wrapup(); -#endif exit(code); } -void set_busy_status(void *frontend, int status) +/* + * Various error message and/or fatal exit functions. + */ +void console_print_error_msg(const char *prefix, const char *msg) +{ + fputs(prefix, stderr); + fputs(": ", stderr); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); +} + +void console_print_error_msg_fmt_v( + const char *prefix, const char *fmt, va_list ap) +{ + char *msg = dupvprintf(fmt, ap); + console_print_error_msg(prefix, msg); + sfree(msg); +} + +void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + console_print_error_msg_fmt_v(prefix, fmt, ap); + va_end(ap); +} + +void modalfatalbox(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + console_print_error_msg_fmt_v("FATAL ERROR", fmt, ap); + va_end(ap); + cleanup_exit(1); +} + +void nonfatal(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + console_print_error_msg_fmt_v("ERROR", fmt, ap); + va_end(ap); } -void notify_remote_exit(void *frontend) +void console_connection_fatal(Seat *seat, const char *msg) { + console_print_error_msg("FATAL ERROR", msg); + cleanup_exit(1); } void timer_change_notify(unsigned long next) { } -int verify_ssh_host_key(void *frontend, char *host, int port, - const char *keytype, char *keystr, char *fingerprint, - void (*callback)(void *ctx, int result), void *ctx) +int console_verify_ssh_host_key( + Seat *seat, const char *host, int port, + const char *keytype, char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) { int ret; HANDLE hin; @@ -150,16 +189,9 @@ int verify_ssh_host_key(void *frontend, char *host, int port, } } -void update_specials_menu(void *frontend) -{ -} - -/* - * Ask whether the selected algorithm is acceptable (since it was - * below the configured 'warn' threshold). - */ -int askalg(void *frontend, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) +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; @@ -199,8 +231,9 @@ int askalg(void *frontend, const char *algtype, const char *algname, } } -int askhk(void *frontend, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, int result), void *ctx) +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; @@ -250,8 +283,9 @@ int askhk(void *frontend, const char *algname, const char *betteralgs, * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). */ -int askappend(void *frontend, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) +static int console_askappend(LogPolicy *lp, Filename *filename, + void (*callback)(void *ctx, int result), + void *ctx) { HANDLE hin; DWORD savemode, i; @@ -330,35 +364,48 @@ void pgp_fingerprints(void) "one. See the manual for more information.\n" "(Note: these fingerprints have nothing to do with SSH!)\n" "\n" - "PuTTY Master Key as of 2015 (RSA, 4096-bit):\n" + "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" " " PGP_MASTER_KEY_FP "\n\n" - "Original PuTTY Master Key (RSA, 1024-bit):\n" - " " PGP_RSA_MASTER_KEY_FP "\n" - "Original PuTTY Master Key (DSA, 1024-bit):\n" - " " PGP_DSA_MASTER_KEY_FP "\n", stdout); + "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR + ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" + " " PGP_PREV_MASTER_KEY_FP "\n", stdout); } -void console_provide_logctx(void *logctx) +static void console_logging_error(LogPolicy *lp, const char *string) { - console_logctx = logctx; + /* 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 logevent(void *frontend, const char *string) +static void console_eventlog(LogPolicy *lp, const char *string) { - log_eventlog(console_logctx, string); + /* Ordinary Event Log entries are displayed in the same way as + * logging errors, but only in verbose mode */ + if (flags & FLAG_VERBOSE) + console_logging_error(lp, string); } static void console_data_untrusted(HANDLE hout, const char *data, int len) { DWORD dummy; - /* FIXME: control-character filtering */ - WriteFile(hout, data, len, &dummy, NULL); + bufchain sanitised; + void *vdata; + + bufchain_init(&sanitised); + sanitise_term_data(&sanitised, data, len); + while (bufchain_size(&sanitised) > 0) { + bufchain_prefix(&sanitised, &vdata, &len); + WriteFile(hout, vdata, len, &dummy, NULL); + bufchain_consume(&sanitised, len); + } } -int console_get_userpass_input(prompts_t *p, - const unsigned char *in, int inlen) +int console_get_userpass_input(prompts_t *p) { - HANDLE hin, hout; + HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; size_t curr_prompt; /* @@ -434,14 +481,11 @@ int console_get_userpass_input(prompts_t *p, len = 0; while (1) { DWORD ret = 0; - BOOL r; prompt_ensure_result_size(pr, len * 5 / 4 + 512); - r = ReadFile(hin, pr->result + len, pr->resultsize - len - 1, - &ret, NULL); - - if (!r || ret == 0) { + if (!ReadFile(hin, pr->result + len, pr->resultsize - len - 1, + &ret, NULL) || ret == 0) { len = -1; break; } @@ -471,10 +515,9 @@ int console_get_userpass_input(prompts_t *p, return 1; /* success */ } -void frontend_keypress(void *handle) -{ - /* - * This is nothing but a stub, in console code. - */ - return; -} +static const LogPolicyVtable default_logpolicy_vt = { + console_eventlog, + console_askappend, + console_logging_error, +}; +LogPolicy default_logpolicy[1] = {{ &default_logpolicy_vt }}; diff --git a/windows/winctrls.c b/windows/winctrls.c index a03967e6..99119cc9 100644 --- a/windows/winctrls.c +++ b/windows/winctrls.c @@ -38,6 +38,21 @@ #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) { @@ -77,7 +92,7 @@ HWND doctl(struct ctlpos *cp, RECT r, 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)); + SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(true, 0)); if (!strcmp(wclass, "LISTBOX")) { /* @@ -150,7 +165,7 @@ void endbox(struct ctlpos *cp) /* * A static line, followed by a full-width edit box. */ -void editboxfw(struct ctlpos *cp, int password, char *text, +void editboxfw(struct ctlpos *cp, bool password, char *text, int staticid, int editid) { RECT r; @@ -519,7 +534,7 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid, /* * A simple push button. */ -void button(struct ctlpos *cp, char *btext, int bid, int defbtn) +void button(struct ctlpos *cp, char *btext, int bid, bool defbtn) { RECT r; @@ -754,7 +769,7 @@ void bigeditctrl(struct ctlpos *cp, char *stext, * A list box with a static labelling it. */ void listbox(struct ctlpos *cp, char *stext, - int sid, int lid, int lines, int multi) + int sid, int lid, int lines, bool multi) { RECT r; @@ -921,7 +936,7 @@ void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid); - MakeDragList(ctl); + p_MakeDragList(ctl); } break; @@ -966,7 +981,7 @@ static void pl_moveitem(HWND hwnd, int listid, int src, int dst) 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); + 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. */ @@ -979,7 +994,7 @@ static void pl_moveitem(HWND hwnd, int listid, int src, int dst) sfree (txt); } -int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll) +int pl_itemfrompt(HWND hwnd, POINT cursor, bool scroll) { int ret; POINT uppoint, downpoint; @@ -996,17 +1011,17 @@ int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll) * current item if the upper edge is closer than * the lower edge, or _below_ it if vice versa. */ - ret = LBItemFromPt(hwnd, cursor, scroll); + ret = p_LBItemFromPt(hwnd, cursor, scroll); if (ret == -1) return ret; - ret = LBItemFromPt(hwnd, cursor, FALSE); + 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 = LBItemFromPt(hwnd, uppoint, FALSE); - downitem = LBItemFromPt(hwnd, downpoint, FALSE); + upitem = p_LBItemFromPt(hwnd, uppoint, false); + downitem = p_LBItemFromPt(hwnd, downpoint, false); if (!updist && upitem != ret) updist = i; if (!downdist && downitem != ret) @@ -1021,12 +1036,12 @@ int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll) * 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 + * return true from handling this message; it has bit 1 set if a * change may have been made in the contents of the list. */ int handle_prefslist(struct prefslist *hdl, int *array, int maxmemb, - int is_dlmsg, HWND hwnd, + bool is_dlmsg, HWND hwnd, WPARAM wParam, LPARAM lParam) { int i; @@ -1047,22 +1062,22 @@ int handle_prefslist(struct prefslist *hdl, SendDlgItemMessage(hwnd, hdl->listid, LB_ADDSTRING, 0, (LPARAM) ""); - hdl->srcitem = LBItemFromPt(dlm->hWnd, dlm->ptCursor, TRUE); - hdl->dragging = 0; + hdl->srcitem = p_LBItemFromPt(dlm->hWnd, dlm->ptCursor, true); + hdl->dragging = false; /* XXX hack Q183115 */ - SetWindowLongPtr(hwnd, DWLP_MSGRESULT, TRUE); + SetWindowLongPtr(hwnd, DWLP_MSGRESULT, true); ret |= 1; break; case DL_CANCELDRAG: - DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */ + p_DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */ SendDlgItemMessage(hwnd, hdl->listid, LB_DELETESTRING, hdl->dummyitem, 0); - hdl->dragging = 0; + hdl->dragging = false; ret |= 1; break; case DL_DRAGGING: - hdl->dragging = 1; - dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE); + hdl->dragging = true; + dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true); if (dest > hdl->dummyitem) dest = hdl->dummyitem; - DrawInsert (hwnd, dlm->hWnd, dest); + p_DrawInsert (hwnd, dlm->hWnd, dest); if (dest >= 0) SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_MOVECURSOR); else @@ -1070,14 +1085,14 @@ int handle_prefslist(struct prefslist *hdl, ret |= 1; break; case DL_DROPPED: if (hdl->dragging) { - dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE); + dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true); if (dest > hdl->dummyitem) dest = hdl->dummyitem; - DrawInsert (hwnd, dlm->hWnd, -1); + p_DrawInsert (hwnd, dlm->hWnd, -1); } SendDlgItemMessage(hwnd, hdl->listid, LB_DELETESTRING, hdl->dummyitem, 0); if (hdl->dragging) { - hdl->dragging = 0; + hdl->dragging = false; if (dest >= 0) { /* Correct for "missing" item. */ if (dest > hdl->srcitem) dest--; @@ -1196,7 +1211,7 @@ void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c) if (c->shortcuts[i] != NO_SHORTCUT) { unsigned char s = tolower((unsigned char)c->shortcuts[i]); assert(!dp->shortcuts[s]); - dp->shortcuts[s] = TRUE; + dp->shortcuts[s] = true; } } @@ -1207,7 +1222,7 @@ void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c) if (c->shortcuts[i] != NO_SHORTCUT) { unsigned char s = tolower((unsigned char)c->shortcuts[i]); assert(dp->shortcuts[s]); - dp->shortcuts[s] = FALSE; + dp->shortcuts[s] = false; } } @@ -1639,7 +1654,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; statictext(&pos, escaped, 1, base_id); staticbtn(&pos, "", base_id+1, "Change...", base_id+2); - data = fontspec_new("", 0, 0, 0); + data = fontspec_new("", false, 0, 0); sfree(escaped); break; default: @@ -1695,7 +1710,7 @@ void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, } static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, - int has_focus) + bool has_focus) { if (has_focus) { if (dp->focused) @@ -1707,9 +1722,8 @@ static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp, } } -union control *dlg_last_focused(union control *ctrl, void *dlg) +union control *dlg_last_focused(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; return dp->focused == ctrl ? dp->lastfocused : dp->focused; } @@ -1717,12 +1731,13 @@ union control *dlg_last_focused(union control *ctrl, void *dlg) * The dialog-box procedure calls this function to handle Windows * messages on a control we manage. */ -int winctrl_handle_command(struct dlgparam *dp, UINT msg, - WPARAM wParam, LPARAM lParam) +bool winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam) { struct winctrl *c; union control *ctrl; - int i, id, ret; + int i, id; + bool ret; static UINT draglistmsg = WM_NULL; /* @@ -1733,7 +1748,7 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING); if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM) - return 0; + return false; /* * Look up the control ID in our data. @@ -1745,7 +1760,7 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, break; } if (!c) - return 0; /* we have nothing to do */ + return false; /* we have nothing to do */ if (msg == WM_DRAWITEM) { /* @@ -1766,14 +1781,14 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, r.top + (r.bottom-r.top-s.cy)/2, (char *)c->data, strlen((char *)c->data)); - return TRUE; + return true; } ctrl = c->ctrl; id = LOWORD(wParam) - c->base_id; if (!ctrl || !ctrl->generic.handler) - return 0; /* nothing we can do here */ + return false; /* nothing we can do here */ /* * From here on we do not issue `return' statements until the @@ -1782,8 +1797,8 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, * to reach the end of this switch statement so that the * subsequent code can test dp->coloursel_wanted(). */ - ret = 0; - dp->coloursel_wanted = FALSE; + ret = false; + dp->coloursel_wanted = false; /* * Now switch on the control type and the message. @@ -1919,7 +1934,7 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, of.lpstrFileTitle = NULL; of.lpstrTitle = ctrl->fileselect.title; of.Flags = 0; - if (request_file(NULL, &of, FALSE, ctrl->fileselect.for_writing)) { + 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); } @@ -1994,9 +2009,9 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, (unsigned char) (cc.rgbResult >> 8) & 0xFF; dp->coloursel_result.b = (unsigned char) (cc.rgbResult >> 16) & 0xFF; - dp->coloursel_result.ok = TRUE; + dp->coloursel_result.ok = true; } else - dp->coloursel_result.ok = FALSE; + dp->coloursel_result.ok = false; ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK); } @@ -2005,9 +2020,9 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg, /* * This function can be called to produce context help on a - * control. Returns TRUE if it has actually launched some help. + * control. Returns true if it has actually launched some help. */ -int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) +bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) { int i; struct winctrl *c; @@ -2022,17 +2037,17 @@ int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) break; } if (!c) - return 0; /* we have nothing to do */ + 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 0; /* no help available for this ctrl */ + return false; /* no help available for this ctrl */ launch_help(hwnd, c->ctrl->generic.helpctx.p); - return 1; + return true; } /* @@ -2052,9 +2067,8 @@ static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl) return NULL; } -void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_RADIO); CheckRadioButton(dp->hwnd, @@ -2063,9 +2077,8 @@ void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton) c->base_id + 1 + whichbutton); } -int dlg_radiobutton_get(union control *ctrl, void *dlg) +int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int i; assert(c && c->ctrl->generic.type == CTRL_RADIO); @@ -2076,42 +2089,37 @@ int dlg_radiobutton_get(union control *ctrl, void *dlg) return 0; } -void dlg_checkbox_set(union control *ctrl, void *dlg, int checked) +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); - CheckDlgButton(dp->hwnd, c->base_id, (checked != 0)); + CheckDlgButton(dp->hwnd, c->base_id, checked); } -int dlg_checkbox_get(union control *ctrl, void *dlg) +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id); } -void dlg_editbox_set(union control *ctrl, void *dlg, char const *text) +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_EDITBOX); SetDlgItemText(dp->hwnd, c->base_id+1, text); } -char *dlg_editbox_get(union control *ctrl, void *dlg) +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; 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, void *dlg) +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && @@ -2123,9 +2131,8 @@ void dlg_listbox_clear(union control *ctrl, void *dlg) SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); } -void dlg_listbox_del(union control *ctrl, void *dlg, int index) +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && @@ -2137,9 +2144,8 @@ void dlg_listbox_del(union control *ctrl, void *dlg, int index) SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_listbox_add(union control *ctrl, void *dlg, char const *text) +void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && @@ -2158,10 +2164,9 @@ void dlg_listbox_add(union control *ctrl, void *dlg, 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, void *dlg, +void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, char const *text, int id) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, msg2, index; assert(c && @@ -2176,9 +2181,8 @@ void dlg_listbox_addwithid(union control *ctrl, void *dlg, SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id); } -int dlg_listbox_getid(union control *ctrl, void *dlg, int index) +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && c->ctrl->generic.type == CTRL_LISTBOX); @@ -2188,9 +2192,8 @@ int dlg_listbox_getid(union control *ctrl, void *dlg, int index) } /* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(union control *ctrl, void *dlg) +int dlg_listbox_index(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg, ret; assert(c && c->ctrl->generic.type == CTRL_LISTBOX); @@ -2208,9 +2211,8 @@ int dlg_listbox_index(union control *ctrl, void *dlg) return ret; } -int dlg_listbox_issel(union control *ctrl, void *dlg, int index) +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_LISTBOX && c->ctrl->listbox.multisel && @@ -2219,9 +2221,8 @@ int dlg_listbox_issel(union control *ctrl, void *dlg, int index) SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0); } -void dlg_listbox_select(union control *ctrl, void *dlg, int index) +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int msg; assert(c && c->ctrl->generic.type == CTRL_LISTBOX && @@ -2230,17 +2231,15 @@ void dlg_listbox_select(union control *ctrl, void *dlg, int index) SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); } -void dlg_text_set(union control *ctrl, void *dlg, char const *text) +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_TEXT); SetDlgItemText(dp->hwnd, c->base_id, text); } -void dlg_label_change(union control *ctrl, void *dlg, char const *text) +void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *escaped = NULL; int id = -1; @@ -2285,17 +2284,15 @@ void dlg_label_change(union control *ctrl, void *dlg, char const *text) } } -void dlg_filesel_set(union control *ctrl, void *dlg, Filename *fn) +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FILESELECT); SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); } -Filename *dlg_filesel_get(union control *ctrl, void *dlg) +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); char *tmp; Filename *ret; @@ -2306,10 +2303,9 @@ Filename *dlg_filesel_get(union control *ctrl, void *dlg) return ret; } -void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) { char *buf, *boldstr; - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); @@ -2329,9 +2325,8 @@ void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec *fs) dlg_auto_set_fixed_pitch_flag(dp); } -FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); assert(c && c->ctrl->generic.type == CTRL_FONTSELECT); return fontspec_copy((FontSpec *)c->data); @@ -2342,29 +2337,26 @@ FontSpec *dlg_fontsel_get(union control *ctrl, void *dlg) * cause the front end (if possible) to delay updating the screen * until it's all complete, thus avoiding flicker. */ -void dlg_update_start(union control *ctrl, void *dlg) +void dlg_update_start(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); if (c && c->ctrl->generic.type == CTRL_LISTBOX) { - SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, FALSE, 0); + SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0); } } -void dlg_update_done(union control *ctrl, void *dlg) +void dlg_update_done(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); if (c && c->ctrl->generic.type == CTRL_LISTBOX) { HWND hw = GetDlgItem(dp->hwnd, c->base_id+1); - SendMessage(hw, WM_SETREDRAW, TRUE, 0); - InvalidateRect(hw, NULL, TRUE); + SendMessage(hw, WM_SETREDRAW, true, 0); + InvalidateRect(hw, NULL, true); } } -void dlg_set_focus(union control *ctrl, void *dlg) +void dlg_set_focus(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; struct winctrl *c = dlg_findbyctrl(dp, ctrl); int id; HWND ctl; @@ -2398,15 +2390,13 @@ void dlg_set_focus(union control *ctrl, void *dlg) * indication to the user. dlg_beep() is a quick and easy generic * error; dlg_error() puts up a message-box or equivalent. */ -void dlg_beep(void *dlg) +void dlg_beep(dlgparam *dp) { - /* struct dlgparam *dp = (struct dlgparam *)dlg; */ MessageBeep(0); } -void dlg_error_msg(void *dlg, const char *msg) +void dlg_error_msg(dlgparam *dp, const char *msg) { - struct dlgparam *dp = (struct dlgparam *)dlg; MessageBox(dp->hwnd, msg, dp->errtitle ? dp->errtitle : NULL, MB_OK | MB_ICONERROR); @@ -2417,16 +2407,14 @@ void dlg_error_msg(void *dlg, const char *msg) * processing is completed, and passes an integer value (typically * a success status). */ -void dlg_end(void *dlg, int value) +void dlg_end(dlgparam *dp, int value) { - struct dlgparam *dp = (struct dlgparam *)dlg; - dp->ended = TRUE; + dp->ended = true; dp->endresult = value; } -void dlg_refresh(union control *ctrl, void *dlg) +void dlg_refresh(union control *ctrl, dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; int i, j; struct winctrl *c; @@ -2452,38 +2440,35 @@ void dlg_refresh(union control *ctrl, void *dlg) } } -void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b) +void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b) { - struct dlgparam *dp = (struct dlgparam *)dlg; - dp->coloursel_wanted = TRUE; + dp->coloursel_wanted = true; dp->coloursel_result.r = r; dp->coloursel_result.g = g; dp->coloursel_result.b = b; } -int dlg_coloursel_results(union control *ctrl, void *dlg, - int *r, int *g, int *b) +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) { - struct dlgparam *dp = (struct dlgparam *)dlg; if (dp->coloursel_result.ok) { *r = dp->coloursel_result.r; *g = dp->coloursel_result.g; *b = dp->coloursel_result.b; - return 1; + return true; } else - return 0; + return false; } -void dlg_auto_set_fixed_pitch_flag(void *dlg) +void dlg_auto_set_fixed_pitch_flag(dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; Conf *conf = (Conf *)dp->data; FontSpec *fs; int quality; HFONT hfont; HDC hdc; TEXTMETRIC tm; - int is_var; + bool is_var; /* * Attempt to load the current font, and see if it's @@ -2498,7 +2483,7 @@ void dlg_auto_set_fixed_pitch_flag(void *dlg) 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, + 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); @@ -2507,7 +2492,7 @@ void dlg_auto_set_fixed_pitch_flag(void *dlg) /* 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 */ + is_var = false; /* assume it's basically normal */ } if (hdc) ReleaseDC(NULL, hdc); @@ -2515,18 +2500,16 @@ void dlg_auto_set_fixed_pitch_flag(void *dlg) DeleteObject(hfont); if (is_var) - dp->fixed_pitch_fonts = FALSE; + dp->fixed_pitch_fonts = false; } -int dlg_get_fixed_pitch_flag(void *dlg) +bool dlg_get_fixed_pitch_flag(dlgparam *dp) { - struct dlgparam *dp = (struct dlgparam *)dlg; return dp->fixed_pitch_fonts; } -void dlg_set_fixed_pitch_flag(void *dlg, int flag) +void dlg_set_fixed_pitch_flag(dlgparam *dp, bool flag) { - struct dlgparam *dp = (struct dlgparam *)dlg; dp->fixed_pitch_fonts = flag; } @@ -2534,12 +2517,12 @@ void dp_init(struct dlgparam *dp) { dp->nctrltrees = 0; dp->data = NULL; - dp->ended = FALSE; + 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; + dp->fixed_pitch_fonts = true; } void dp_add_tree(struct dlgparam *dp, struct winctrls *wc) diff --git a/windows/windefs.c b/windows/windefs.c index 24a2ea41..308c29eb 100644 --- a/windows/windefs.c +++ b/windows/windefs.c @@ -9,9 +9,9 @@ FontSpec *platform_default_fontspec(const char *name) { if (!strcmp(name, "Font")) - return fontspec_new("Courier New", 0, 10, ANSI_CHARSET); + return fontspec_new("Courier New", false, 10, ANSI_CHARSET); else - return fontspec_new("", 0, 0, 0); + return fontspec_new("", false, 0, 0); } Filename *platform_default_filename(const char *name) @@ -29,6 +29,11 @@ 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; diff --git a/windows/windlg.c b/windows/windlg.c index e29f1291..16abe3ff 100644 --- a/windows/windlg.c +++ b/windows/windlg.c @@ -42,29 +42,40 @@ static struct controlbox *ctrlbox; static struct winctrls ctrls_base, ctrls_panel; static struct dlgparam dp; -static char **events = NULL; -static int nevents = 0, negsize = 0; +#define LOGEVENT_INITIAL_MAX 128 +#define LOGEVENT_CIRCULAR_MAX 128 -extern Conf *conf; /* defined in window.c */ +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 int recurse = 0; + static bool recurse = false; WINDOWPLACEMENT wp; if (recurse) return; - recurse = 1; + recurse = true; wp.length = sizeof(wp); if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { wp.showCmd = SW_SHOWNORMAL; SetWindowPlacement(hwnd, &wp); } - recurse = 0; + 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 INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg, @@ -84,9 +95,12 @@ static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg, SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2, (LPARAM) tabs); } - for (i = 0; i < nevents; i++) + 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[i]); + 0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]); return 1; case WM_COMMAND: switch (LOWORD(wParam)) { @@ -127,27 +141,27 @@ static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg, size = 0; for (i = 0; i < count; i++) size += - strlen(events[selitems[i]]) + sizeof(sel_nl); + strlen(getevent(selitems[i])) + sizeof(sel_nl); clipdata = snewn(size, char); if (clipdata) { char *p = clipdata; for (i = 0; i < count; i++) { - char *q = events[selitems[i]]; + 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(NULL, clipdata, size, TRUE); + write_aclip(CLIP_SYSTEM, clipdata, size, true); sfree(clipdata); } sfree(selitems); - for (i = 0; i < nevents; i++) + for (i = 0; i < (ninitial + ncircular); i++) SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL, - FALSE, i); + false, i); } } return 0; @@ -214,7 +228,7 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: - EndDialog(hwnd, TRUE); + EndDialog(hwnd, true); return 0; case IDA_LICENCE: EnableWindow(hwnd, 0); @@ -227,13 +241,13 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, case IDA_WEB: /* Load web browser */ ShellExecute(hwnd, "open", - "http://www.chiark.greenend.org.uk/~sgtatham/putty/", + "https://www.chiark.greenend.org.uk/~sgtatham/putty/", 0, 0, SW_SHOWDEFAULT); return 0; } return 0; case WM_CLOSE: - EndDialog(hwnd, TRUE); + EndDialog(hwnd, true); return 0; } return 0; @@ -412,7 +426,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } /* @@ -435,7 +449,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, hwnd, (HMENU) IDCX_TVSTATIC, hinst, NULL); font = SendMessage(hwnd, WM_GETFONT, 0, 0); - SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0)); + SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(true, 0)); r.left = 3; r.right = r.left + 95; @@ -452,7 +466,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, hwnd, (HMENU) IDCX_TREEVIEW, hinst, NULL); font = SendMessage(hwnd, WM_GETFONT, 0, 0); - SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0)); + SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(true, 0)); tvfaff.treeview = treeview; memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat)); } @@ -578,7 +592,7 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); - SendMessage (hwnd, WM_SETREDRAW, FALSE, 0); + SendMessage (hwnd, WM_SETREDRAW, false, 0); item.hItem = i; item.pszText = buffer; @@ -607,8 +621,8 @@ static INT_PTR CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, dlg_refresh(NULL, &dp); /* set up control values */ - SendMessage (hwnd, WM_SETREDRAW, TRUE, 0); - InvalidateRect (hwnd, NULL, TRUE); + SendMessage (hwnd, WM_SETREDRAW, true, 0); + InvalidateRect (hwnd, NULL, true); SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */ return 0; @@ -677,13 +691,13 @@ void defuse_showwindow(void) } } -int do_config(void) +bool do_config(void) { - int ret; + bool ret; ctrlbox = ctrl_new_box(); - setup_config_box(ctrlbox, FALSE, 0, 0); - win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0); + 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); @@ -693,7 +707,7 @@ int do_config(void) dp.errtitle = dupprintf("%s Error", appname); dp.data = conf; dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */ + dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, @@ -707,17 +721,18 @@ int do_config(void) return ret; } -int do_reconfig(HWND hwnd, int protcfginfo) +bool do_reconfig(HWND hwnd, int protcfginfo) { Conf *backup_conf; - int ret, protocol; + 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); + 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); @@ -727,7 +742,7 @@ int do_reconfig(HWND hwnd, int protcfginfo) dp.errtitle = dupprintf("%s Error", appname); dp.data = conf; dlg_auto_set_fixed_pitch_flag(&dp); - dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */ + dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, GenericMainDlgProc); @@ -745,32 +760,47 @@ int do_reconfig(HWND hwnd, int protcfginfo) return ret; } -void logevent(void *frontend, const char *string) +static void win_gui_eventlog(LogPolicy *lp, const char *string) { char timebuf[40]; + char **location; struct tm tm; - log_eventlog(logctx, string); - - if (nevents >= negsize) { - negsize += 64; - events = sresize(events, negsize, char *); - } - tm=ltime(); strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); - events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); - strcpy(events[nevents], timebuf); - strcat(events[nevents], string); + if (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, (const char *)NULL); if (logbox) { int count; SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) events[nevents]); + 0, (LPARAM) *location); count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0); SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0); } - nevents++; + 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) +{ + /* Send 'can't open log file' errors to the terminal window. + * (Marked as stderr, although terminal.c won't care.) */ + seat_stderr(win_seat, event, strlen(event)); + seat_stderr(win_seat, "\r\n", 2); } void showeventlog(HWND hwnd) @@ -788,9 +818,10 @@ void showabout(HWND hwnd) DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); } -int verify_ssh_host_key(void *frontend, char *host, int port, - const char *keytype, char *keystr, char *fingerprint, - void (*callback)(void *ctx, int result), void *ctx) +int win_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, + const char *keytype, char *keystr, char *fingerprint, + void (*callback)(void *ctx, int result), void *ctx) { int ret; @@ -872,8 +903,9 @@ int verify_ssh_host_key(void *frontend, char *host, int port, * Ask whether the selected algorithm is acceptable (since it was * below the configured 'warn' threshold). */ -int askalg(void *frontend, const char *algtype, const char *algname, - void (*callback)(void *ctx, int result), void *ctx) +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[] = @@ -897,8 +929,9 @@ int askalg(void *frontend, const char *algtype, const char *algname, return 0; } -int askhk(void *frontend, const char *algname, const char *betteralgs, - 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) { static const char mbtitle[] = "%s Security Alert"; static const char msg[] = @@ -928,8 +961,9 @@ int askhk(void *frontend, const char *algname, const char *betteralgs, * Ask whether to wipe a session log file before writing to it. * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). */ -int askappend(void *frontend, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) +static 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" @@ -961,6 +995,13 @@ int askappend(void *frontend, Filename *filename, return 0; } +static const LogPolicyVtable default_logpolicy_vt = { + win_gui_eventlog, + win_gui_askappend, + win_gui_logging_error, +}; +LogPolicy default_logpolicy[1] = {{ &default_logpolicy_vt }}; + /* * Warn about the obsolescent key file format. * diff --git a/windows/window.c b/windows/window.c index 004eb4f8..913e453d 100644 --- a/windows/window.c +++ b/windows/window.c @@ -24,6 +24,7 @@ #include "storage.h" #include "win_res.h" #include "winsecur.h" +#include "tree234.h" #ifndef NO_MULTIMON #include @@ -51,7 +52,8 @@ #define IDM_SAVEDSESS 0x0160 #define IDM_COPYALL 0x0170 #define IDM_FULLSCREEN 0x0180 -#define IDM_PASTE 0x0190 +#define IDM_COPY 0x0190 +#define IDM_PASTE 0x01A0 #define IDM_SPECIALSEP 0x0200 #define IDM_SPECIAL_MIN 0x0400 @@ -100,43 +102,41 @@ static void set_input_locale(HKL); static void update_savedsess_menu(void); static void init_winfuncs(void); -static int is_full_screen(void); +static bool is_full_screen(void); static void make_full_screen(void); static void clear_full_screen(void); static void flip_full_screen(void); -static int process_clipdata(HGLOBAL clipdata, int unicode); +static void process_clipdata(HGLOBAL clipdata, bool unicode); +static void setup_clipboards(Terminal *, Conf *); /* Window layout information */ static void reset_window(int); static int extra_width, extra_height; -static int font_width, font_height, font_dualwidth, font_varpitch; +static int font_width, font_height; +static bool font_dualwidth, font_varpitch; static int offset_width, offset_height; -static int was_zoomed = 0; +static bool was_zoomed = false; static int prev_rows, prev_cols; static void flash_window(int mode); static void sys_cursor_update(void); -static int get_fullscreen_rect(RECT * ss); +static bool get_fullscreen_rect(RECT * ss); static int caret_x = -1, caret_y = -1; static int kbd_codepage; -static void *ldisc; -static Backend *back; -static void *backhandle; +static Ldisc *ldisc; +static Backend *backend; static struct unicode_data ucsdata; -static int session_closed; -static int reconfiguring = FALSE; +static bool session_closed; +static bool reconfiguring = false; -static const struct telnet_special *specials = NULL; +static const SessionSpecial *specials = NULL; static HMENU specials_menu = NULL; static int n_specials = 0; -static wchar_t *clipboard_contents; -static size_t clipboard_length; - #define TIMING_TIMER_ID 1234 static long timing_next_time; @@ -152,8 +152,6 @@ struct wm_netevent_params { LPARAM lParam; }; -Conf *conf; /* exported to windlg.c */ - static void conf_cache_data(void); int cursor_type; int vtmode; @@ -184,11 +182,11 @@ struct agent_callback { #define FONT_SHIFT 5 static HFONT fonts[FONT_MAXNO]; static LOGFONT lfont; -static int fontflag[FONT_MAXNO]; +static bool fontflag[FONT_MAXNO]; static enum { BOLD_NONE, BOLD_SHADOW, BOLD_FONT } bold_font_mode; -static int bold_colours; +static bool bold_colours; static enum { UND_LINE, UND_FONT } und_mode; @@ -198,6 +196,9 @@ static int descent; #define NEXTCOLOURS 240 #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS) static COLORREF colours[NALLCOLOURS]; +struct rgb { + int r, g, b; +} colours_rgb[NALLCOLOURS]; static HPALETTE pal; static LPLOGPALETTE logpal; static RGBTRIPLE defpal[NALLCOLOURS]; @@ -208,10 +209,10 @@ static int dbltime, lasttime, lastact; static Mouse_Button lastbtn; /* this allows xterm-style mouse handling. */ -static int send_raw_mouse = 0; +static bool send_raw_mouse = false; static int wheel_accumulator = 0; -static int busy_status = BUSY_NOT; +static BusyStatus busy_status = BUSY_NOT; static char *window_name, *icon_name; @@ -225,26 +226,133 @@ static UINT wm_mousewheel = WM_MOUSEWHEEL; (((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \ ((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */ -const int share_can_be_downstream = TRUE; -const int share_can_be_upstream = TRUE; +static bool wintw_setup_draw_ctx(TermWin *); +static void wintw_draw_text(TermWin *, int x, int y, wchar_t *text, int len, + unsigned long attrs, int lattrs, truecolour tc); +static void wintw_draw_cursor(TermWin *, int x, int y, wchar_t *text, int len, + unsigned long attrs, int lattrs, truecolour tc); +static int wintw_char_width(TermWin *, int uc); +static void wintw_free_draw_ctx(TermWin *); +static void wintw_set_cursor_pos(TermWin *, int x, int y); +static void wintw_set_raw_mouse_mode(TermWin *, bool enable); +static void wintw_set_scrollbar(TermWin *, int total, int start, int page); +static void wintw_bell(TermWin *, int mode); +static void wintw_clip_write( + TermWin *, int clipboard, wchar_t *text, int *attrs, + truecolour *colours, int len, bool must_deselect); +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_minimised(TermWin *, bool minimised); +static bool wintw_is_minimised(TermWin *); +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 bool wintw_palette_get(TermWin *, int n, int *r, int *g, int *b); +static void wintw_palette_set(TermWin *, int n, int r, int g, int b); +static void wintw_palette_reset(TermWin *); +static void wintw_get_pos(TermWin *, int *x, int *y); +static void wintw_get_pixels(TermWin *, int *x, int *y); +static const char *wintw_get_title(TermWin *, bool icon); +static bool wintw_is_utf8(TermWin *); + +static const TermWinVtable windows_termwin_vt = { + wintw_setup_draw_ctx, + wintw_draw_text, + wintw_draw_cursor, + wintw_char_width, + wintw_free_draw_ctx, + wintw_set_cursor_pos, + wintw_set_raw_mouse_mode, + wintw_set_scrollbar, + wintw_bell, + wintw_clip_write, + wintw_clip_request_paste, + wintw_refresh, + wintw_request_resize, + wintw_set_title, + wintw_set_icon_title, + wintw_set_minimised, + wintw_is_minimised, + wintw_set_maximised, + wintw_move, + wintw_set_zorder, + wintw_palette_get, + wintw_palette_set, + wintw_palette_reset, + wintw_get_pos, + wintw_get_pixels, + wintw_get_title, + wintw_is_utf8, +}; + +static TermWin wintw[1]; +static HDC wintw_hdc; -/* Dummy routine, only required in plink. */ -void frontend_echoedit_update(void *frontend, int echo, int edit) +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = true; + +static bool is_utf8(void) { + return ucsdata.line_codepage == CP_UTF8; } -int frontend_is_utf8(void *frontend) +static bool wintw_is_utf8(TermWin *tw) { - return ucsdata.line_codepage == CP_UTF8; + return is_utf8(); } -char *get_ttymode(void *frontend, const char *mode) +static bool win_seat_is_utf8(Seat *seat) +{ + return is_utf8(); +} + +char *win_seat_get_ttymode(Seat *seat, const char *mode) { return term_get_ttymode(term, mode); } +bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y) +{ + win_get_pixels(wintw, x, y); + return true; +} + +static int win_seat_output(Seat *seat, bool is_stderr, const void *, int); +static bool win_seat_eof(Seat *seat); +static int win_seat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input); +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 const SeatVtable win_seat_vt = { + win_seat_output, + win_seat_eof, + win_seat_get_userpass_input, + win_seat_notify_remote_exit, + win_seat_connection_fatal, + win_seat_update_specials_menu, + win_seat_get_ttymode, + win_seat_set_busy_status, + win_seat_verify_ssh_host_key, + win_seat_confirm_weak_crypto_primitive, + win_seat_confirm_weak_cached_hostkey, + win_seat_is_utf8, + nullseat_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + win_seat_get_window_pixel_size, +}; +static Seat win_seat_impl = { &win_seat_vt }; +Seat *const win_seat = &win_seat_impl; + static void start_backend(void) { + const struct BackendVtable *vt; const char *error; char msg[1024], *title; char *realhost; @@ -254,8 +362,8 @@ static void start_backend(void) * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - back = backend_from_proto(conf_get_int(conf, CONF_protocol)); - if (back == NULL) { + vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + if (!vt) { char *str = dupprintf("%s Internal Error", appname); MessageBox(NULL, "Unsupported protocol number found", str, MB_OK | MB_ICONEXCLAMATION); @@ -263,13 +371,12 @@ static void start_backend(void) cleanup_exit(1); } - error = back->init(NULL, &backhandle, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, - conf_get_int(conf, CONF_tcp_nodelay), - conf_get_int(conf, CONF_tcp_keepalives)); - back->provide_logctx(backhandle, logctx); + error = backend_init(vt, win_seat, &backend, logctx, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, + conf_get_bool(conf, CONF_tcp_nodelay), + conf_get_bool(conf, CONF_tcp_keepalives)); if (error) { char *str = dupprintf("%s Error", appname); sprintf(msg, "Unable to open connection to\n" @@ -285,18 +392,18 @@ static void start_backend(void) title = msg; } sfree(realhost); - set_title(NULL, title); - set_icon(NULL, title); + win_set_title(wintw, title); + win_set_icon_title(wintw, title); /* * Connect the terminal to the backend for resize purposes. */ - term_provide_resize_fn(term, back->size, backhandle); + term_provide_backend(term, backend); /* * Set up a line discipline. */ - ldisc = ldisc_create(conf, term, back, backhandle, NULL); + ldisc = ldisc_create(conf, term, backend, win_seat); /* * Destroy the Restart Session menu item. (This will return @@ -309,7 +416,7 @@ static void start_backend(void) DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); } - session_closed = FALSE; + session_closed = false; } static void close_session(void *ignored_context) @@ -317,21 +424,20 @@ static void close_session(void *ignored_context) char morestuff[100]; int i; - session_closed = TRUE; + session_closed = true; sprintf(morestuff, "%.70s (inactive)", appname); - set_icon(NULL, morestuff); - set_title(NULL, morestuff); + win_set_icon_title(wintw, morestuff); + win_set_title(wintw, morestuff); if (ldisc) { ldisc_free(ldisc); ldisc = NULL; } - if (back) { - back->free(backhandle); - backhandle = NULL; - back = NULL; - term_provide_resize_fn(term, NULL, NULL); - update_specials_menu(NULL); + if (backend) { + backend_free(backend); + backend = NULL; + term_provide_backend(term, NULL); + seat_update_specials_menu(win_seat); } /* @@ -356,10 +462,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) hinst = inst; hwnd = NULL; flags = FLAG_VERBOSE | FLAG_INTERACTIVE; + cmdline_tooltype |= TOOLTYPE_HOST_ARG | TOOLTYPE_PORT_ARG; sk_init(); - InitCommonControls(); + init_common_controls(); /* Set Explicit App User Model Id so that jump lists don't cause PuTTY to hang on to removable media. */ @@ -370,23 +477,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * config box. */ defuse_showwindow(); - if (!init_winver()) - { - char *str = dupprintf("%s Fatal Error", appname); - MessageBox(NULL, "Windows refuses to report a version", - str, MB_OK | MB_ICONEXCLAMATION); - sfree(str); - return 1; - } + init_winver(); /* * If we're running a version of Windows that doesn't support * WM_MOUSEWHEEL, find out what message number we should be * using instead. */ - if (osVersion.dwMajorVersion < 4 || - (osVersion.dwMajorVersion == 4 && - osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT)) + if (osMajorVersion < 4 || + (osMajorVersion == 4 && osPlatformId != VER_PLATFORM_WIN32_NT)) wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG"); init_help(); @@ -412,19 +511,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) */ { char *p; - int got_host = 0; - /* By default, we bring up the config dialog, rather than launching - * a session. This gets set to TRUE if something happens to change - * that (e.g., a hostname is specified on the command-line). */ - int allow_launch = FALSE; + bool special_launchable_argument = false; default_protocol = be_default_protocol; /* Find the appropriate default port. */ { - Backend *b = backend_from_proto(default_protocol); + const struct BackendVtable *vt = + backend_vt_from_proto(default_protocol); default_port = 0; /* illegal */ - if (b) - default_port = b->default_port; + if (vt) + default_port = vt->default_port; } conf_set_int(conf, CONF_logtype, LGTYP_NONE); @@ -446,7 +542,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) (!p[2] || p[2] == '@' || p[2] == '&')) { /* &R restrict-acl prefix */ restrict_process_acl(); - restricted_acl = TRUE; + restricted_acl = true; p += 2; } @@ -466,7 +562,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (!conf_launchable(conf) && !do_config()) { cleanup_exit(0); } - allow_launch = TRUE; /* allow it to be launched directly */ + special_launchable_argument = true; } else if (*p == '&') { /* * An initial & means we've been given a command line @@ -480,13 +576,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) == 2 && (cp = MapViewOfFile(filemap, FILE_MAP_READ, 0, 0, cpsize)) != NULL) { - conf_deserialise(conf, cp, cpsize); + 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()) { cleanup_exit(0); } - allow_launch = TRUE; + 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 @@ -541,52 +640,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) pgp_fingerprints(); exit(1); } else if (*p != '-') { - char *q = p; - if (got_host) { - /* - * If we already have a host name, treat - * this argument as a port number. NB we - * have to treat this as a saved -P - * argument, so that it will be deferred - * until it's a good moment to run it. - */ - int ret = cmdline_process_param("-P", p, 1, conf); - assert(ret == 2); - } else if (!strncmp(q, "telnet:", 7)) { - /* - * If the hostname starts with "telnet:", - * set the protocol to Telnet and process - * the string as a Telnet URL. - */ - char c; - - q += 7; - if (q[0] == '/' && q[1] == '/') - q += 2; - conf_set_int(conf, CONF_protocol, PROT_TELNET); - p = q; - p += host_strcspn(p, ":/"); - c = *p; - if (*p) - *p++ = '\0'; - if (c == ':') - conf_set_int(conf, CONF_port, atoi(p)); - else - conf_set_int(conf, CONF_port, -1); - conf_set_str(conf, CONF_host, q); - got_host = 1; - } else { - /* - * Otherwise, treat this argument as a host - * name. - */ - while (*p && !isspace(*p)) - p++; - if (*p) - *p++ = '\0'; - conf_set_str(conf, CONF_host, q); - got_host = 1; - } + cmdline_error("unexpected argument \"%s\"", p); } else { cmdline_error("unknown option \"%s\"", p); } @@ -595,70 +649,16 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) cmdline_run_saved(conf); - if (loaded_session || got_host) - allow_launch = TRUE; - - if ((!allow_launch || !conf_launchable(conf)) && !do_config()) { - cleanup_exit(0); - } - /* - * Muck about with the hostname in various ways. - */ - { - char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); - char *host = hostbuf; - char *p, *q; - - /* - * Trim leading whitespace. - */ - host += strspn(host, " \t"); - - /* - * See if host is of the form user@host, and separate - * out the username if so. - */ - if (host[0] != '\0') { - char *atsign = strrchr(host, '@'); - if (atsign) { - *atsign = '\0'; - conf_set_str(conf, CONF_username, host); - host = atsign + 1; - } - } - - /* - * Trim a colon suffix off the hostname if it's there. In - * order to protect unbracketed IPv6 address literals - * against this treatment, we do not do this if there's - * _more_ than one colon. - */ - { - char *c = host_strchr(host, ':'); - - if (c) { - char *d = host_strchr(c+1, ':'); - if (!d) - *c = '\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); + * 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()) + cleanup_exit(0); } + + prepare_session(conf); } if (!prev) { @@ -710,13 +710,13 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL; int exwinmode = 0; wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - if (!conf_get_int(conf, CONF_scrollbar)) + if (!conf_get_bool(conf, CONF_scrollbar)) winmode &= ~(WS_VSCROLL); if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); - if (conf_get_int(conf, CONF_alwaysontop)) + if (conf_get_bool(conf, CONF_alwaysontop)) exwinmode |= WS_EX_TOPMOST; - if (conf_get_int(conf, CONF_sunken_edge)) + if (conf_get_bool(conf, CONF_sunken_edge)) exwinmode |= WS_EX_CLIENTEDGE; hwnd = CreateWindowExW(exwinmode, uappname, uappname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, @@ -737,8 +737,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * which will call schedule_timer(), which will in turn call * timer_change_notify() which will expect hwnd to exist.) */ - term = term_init(conf, &ucsdata, NULL); - logctx = log_init(NULL, conf); + wintw->vt = &windows_termwin_vt; + term = term_init(conf, &ucsdata, wintw); + setup_clipboards(term, conf); + logctx = log_init(default_logpolicy, conf); term_provide_logctx(term, logctx); term_size(term, conf_get_int(conf, CONF_height), conf_get_int(conf, CONF_width), @@ -790,7 +792,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) si.nMax = term->rows - 1; si.nPage = term->rows; si.nPos = 0; - SetScrollInfo(hwnd, SB_VERT, &si, FALSE); + SetScrollInfo(hwnd, SB_VERT, &si, false); } /* @@ -808,12 +810,13 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) int j; char *str; - popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE); + popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, false); popup_menus[CTXMENU].menu = CreatePopupMenu(); + AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_COPY, "&Copy"); AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste"); savedsess_menu = CreateMenu(); - get_sesslist(&sesslist, TRUE); + get_sesslist(&sesslist, true); update_savedsess_menu(); for (j = 0; j < lenof(popup_menus); j++) { @@ -845,7 +848,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) } if (restricted_acl) { - logevent(NULL, "Running with restricted process ACL"); + lp_eventlog(default_logpolicy, "Running with restricted process ACL"); } start_backend(); @@ -904,7 +907,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) handles = handle_get_events(&nhandles); - n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, + n = MsgWaitForMultipleObjects(nhandles, handles, false, timeout, QS_ALLINPUT); if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { @@ -951,6 +954,30 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) return msg.wParam; /* ... but optimiser doesn't know */ } +static void setup_clipboards(Terminal *term, Conf *conf) +{ + assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); + + term->n_mouse_select_clipboards = 1; + + if (conf_get_bool(conf, CONF_mouseautocopy)) { + term->mouse_select_clipboards[ + term->n_mouse_select_clipboards++] = CLIP_SYSTEM; + } + + switch (conf_get_int(conf, CONF_mousepaste)) { + case CLIPUI_IMPLICIT: + term->mouse_paste_clipboard = CLIP_LOCAL; + break; + case CLIPUI_EXPLICIT: + term->mouse_paste_clipboard = CLIP_SYSTEM; + break; + default: + term->mouse_paste_clipboard = CLIP_NULL; + break; + } +} + /* * Clean up and exit. */ @@ -967,9 +994,6 @@ void cleanup_exit(int code) if (conf_get_int(conf, CONF_protocol) == PROT_SSH) { random_save_seed(); -#ifdef MSCRYPTOAPI - crypto_wrapup(); -#endif } shutdown_help(); @@ -982,7 +1006,7 @@ void cleanup_exit(int code) /* * Set up, or shut down, an AsyncSelect. Called from winnet.c. */ -char *do_select(SOCKET skt, int startup) +char *do_select(SOCKET skt, bool startup) { int msg, events; if (startup) { @@ -1027,13 +1051,13 @@ static void update_savedsess_menu(void) /* * Update the Special Commands submenu. */ -void update_specials_menu(void *frontend) +static void win_seat_update_specials_menu(Seat *seat) { HMENU new_menu; int i, j; - if (back) - specials = back->get_specials(backhandle); + if (backend) + specials = backend_get_specials(backend); else specials = NULL; @@ -1046,10 +1070,10 @@ void update_specials_menu(void *frontend) for (i = 0; nesting > 0; i++) { assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX); switch (specials[i].code) { - case TS_SEP: + case SS_SEP: AppendMenu(new_menu, MF_SEPARATOR, 0, 0); break; - case TS_SUBMENU: + case SS_SUBMENU: assert(nesting < 2); nesting++; saved_menu = new_menu; /* XXX lame stacking */ @@ -1057,7 +1081,7 @@ void update_specials_menu(void *frontend) AppendMenu(saved_menu, MF_POPUP | MF_ENABLED, (UINT_PTR) new_menu, specials[i].name); break; - case TS_EXITMENU: + case SS_EXITMENU: nesting--; if (nesting) { new_menu = saved_menu; /* XXX lame stacking */ @@ -1097,9 +1121,9 @@ void update_specials_menu(void *frontend) static void update_mouse_pointer(void) { - LPTSTR curstype; - int force_visible = FALSE; - static int forced_visible = FALSE; + LPTSTR curstype = NULL; + bool force_visible = false; + static bool forced_visible = false; switch (busy_status) { case BUSY_NOT: if (send_raw_mouse) @@ -1109,11 +1133,11 @@ static void update_mouse_pointer(void) break; case BUSY_WAITING: curstype = IDC_APPSTARTING; /* this may be an abuse */ - force_visible = TRUE; + force_visible = true; break; case BUSY_CPU: curstype = IDC_WAIT; - force_visible = TRUE; + force_visible = true; break; default: assert(0); @@ -1133,7 +1157,7 @@ static void update_mouse_pointer(void) } } -void set_busy_status(void *frontend, int status) +static void win_seat_set_busy_status(Seat *seat, BusyStatus status) { busy_status = status; update_mouse_pointer(); @@ -1142,9 +1166,9 @@ void set_busy_status(void *frontend, int status) /* * set or clear the "raw mouse message" mode */ -void set_raw_mouse_mode(void *frontend, int activate) +static void wintw_set_raw_mouse_mode(TermWin *tw, bool activate) { - activate = activate && !conf_get_int(conf, CONF_no_mouse_rep); + activate = activate && !conf_get_bool(conf, CONF_no_mouse_rep); send_raw_mouse = activate; update_mouse_pointer(); } @@ -1152,17 +1176,12 @@ void set_raw_mouse_mode(void *frontend, int activate) /* * Print a message box and close the connection. */ -void connection_fatal(void *frontend, const char *fmt, ...) +static void win_seat_connection_fatal(Seat *seat, const char *msg) { - va_list ap; - char *stuff, morestuff[100]; + char title[100]; - va_start(ap, fmt); - stuff = dupvprintf(fmt, ap); - va_end(ap); - sprintf(morestuff, "%.70s Fatal Error", appname); - MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); - sfree(stuff); + sprintf(title, "%.70s Fatal Error", appname); + MessageBox(hwnd, msg, title, MB_ICONERROR | MB_OK); if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON) PostQuitMessage(1); @@ -1232,7 +1251,7 @@ static void conftopalette(void) } /* Override with system colours if appropriate */ - if (conf_get_int(conf, CONF_system_colour)) + if (conf_get_bool(conf, CONF_system_colour)) systopalette(); } @@ -1264,6 +1283,19 @@ static void systopalette(void) } } +static void internal_set_colour(int i, int r, int g, int b) +{ + assert(i >= 0); + assert(i < NALLCOLOURS); + if (pal) + colours[i] = PALETTERGB(r, g, b); + else + colours[i] = RGB(r, g, b); + colours_rgb[i].r = r; + colours_rgb[i].g = g; + colours_rgb[i].b = b; +} + /* * Set up the colour palette. */ @@ -1272,7 +1304,7 @@ static void init_palette(void) int i; HDC hdc = GetDC(hwnd); if (hdc) { - if (conf_get_int(conf, CONF_try_palette) && + if (conf_get_bool(conf, CONF_try_palette) && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { /* * This is a genuine case where we must use smalloc @@ -1291,22 +1323,16 @@ static void init_palette(void) } pal = CreatePalette(logpal); if (pal) { - SelectPalette(hdc, pal, FALSE); + SelectPalette(hdc, pal, false); RealizePalette(hdc); - SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE); + SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); } } ReleaseDC(hwnd, hdc); } - if (pal) - for (i = 0; i < NALLCOLOURS; i++) - colours[i] = PALETTERGB(defpal[i].rgbtRed, - defpal[i].rgbtGreen, - defpal[i].rgbtBlue); - else - for (i = 0; i < NALLCOLOURS; i++) - colours[i] = RGB(defpal[i].rgbtRed, - defpal[i].rgbtGreen, defpal[i].rgbtBlue); + for (i = 0; i < NALLCOLOURS; i++) + internal_set_colour(i, defpal[i].rgbtRed, + defpal[i].rgbtGreen, defpal[i].rgbtBlue); } /* @@ -1317,7 +1343,7 @@ static void init_palette(void) */ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, unsigned short *lpString, UINT cbCount, - CONST INT *lpDx, int opaque) + CONST INT *lpDx, bool opaque) { #ifdef __LCC__ /* @@ -1359,15 +1385,16 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, */ static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, unsigned short *lpString, UINT cbCount, - CONST INT *lpDx, int opaque) + CONST INT *lpDx, bool opaque) { int i, j, xp, xn; - int bkmode = 0, got_bkmode = FALSE; + int bkmode = 0; + bool got_bkmode = false; xp = xn = x; for (i = 0; i < (int)cbCount ;) { - int rtl = is_rtl(lpString[i]); + bool rtl = is_rtl(lpString[i]); xn += lpDx[i]; @@ -1395,9 +1422,9 @@ static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc, xp = xn; bkmode = GetBkMode(hdc); - got_bkmode = TRUE; + got_bkmode = true; SetBkMode(hdc, TRANSPARENT); - opaque = FALSE; + opaque = false; } if (got_bkmode) @@ -1416,8 +1443,8 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm) ABCFLOAT widths[LAST-FIRST + 1]; int j; - font_varpitch = TRUE; - font_dualwidth = TRUE; + font_varpitch = true; + font_dualwidth = true; if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) { ret = 0; for (j = 0; j < lenof(widths); j++) { @@ -1468,7 +1495,7 @@ static void init_fonts(int pick_width, int pick_height) bold_font_mode = conf_get_int(conf, CONF_bold_style) & 1 ? BOLD_FONT : BOLD_NONE; - bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? TRUE : FALSE; + bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? true : false; und_mode = UND_FONT; font = conf_get_fontspec(conf, CONF_font); @@ -1495,12 +1522,12 @@ static void init_fonts(int pick_width, int pick_height) quality = conf_get_int(conf, CONF_font_quality); #define f(i,c,w,u) \ - fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \ + fonts[i] = CreateFont (font_height, font_width, 0, 0, w, false, u, false, \ c, OUT_DEFAULT_PRECIS, \ CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), \ FIXED_PITCH | FF_DONTCARE, font->name) - f(FONT_NORMAL, font->charset, fw_dontcare, FALSE); + f(FONT_NORMAL, font->charset, fw_dontcare, false); SelectObject(hdc, fonts[FONT_NORMAL]); GetTextMetrics(hdc, &tm); @@ -1509,11 +1536,11 @@ static void init_fonts(int pick_width, int pick_height) /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) { - font_varpitch = FALSE; + font_varpitch = false; font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth); } else { - font_varpitch = TRUE; - font_dualwidth = TRUE; + font_varpitch = true; + font_dualwidth = true; } if (pick_width == 0 || pick_height == 0) { font_height = tm.tmHeight; @@ -1544,7 +1571,7 @@ static void init_fonts(int pick_width, int pick_height) ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); } - f(FONT_UNDERLINE, font->charset, fw_dontcare, TRUE); + f(FONT_UNDERLINE, font->charset, fw_dontcare, true); /* * Some fonts, e.g. 9-pt Courier, draw their underlines @@ -1566,7 +1593,8 @@ static void init_fonts(int pick_width, int pick_height) { HDC und_dc; HBITMAP und_bm, und_oldbm; - int i, gotit; + int i; + bool gotit; COLORREF c; und_dc = CreateCompatibleDC(hdc); @@ -1578,11 +1606,11 @@ static void init_fonts(int pick_width, int pick_height) SetBkColor(und_dc, RGB(0, 0, 0)); SetBkMode(und_dc, OPAQUE); ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL); - gotit = FALSE; + gotit = false; for (i = 0; i < font_height; i++) { c = GetPixel(und_dc, font_width / 2, i); if (c != RGB(0, 0, 0)) - gotit = TRUE; + gotit = true; } SelectObject(und_dc, und_oldbm); DeleteObject(und_bm); @@ -1595,7 +1623,7 @@ static void init_fonts(int pick_width, int pick_height) } if (bold_font_mode == BOLD_FONT) { - f(FONT_BOLD, font->charset, fw_bold, FALSE); + f(FONT_BOLD, font->charset, fw_bold, false); } #undef f @@ -1627,7 +1655,9 @@ static void init_fonts(int pick_width, int pick_height) DeleteObject(fonts[FONT_BOLD]); fonts[FONT_BOLD] = 0; } - fontflag[0] = fontflag[1] = fontflag[2] = 1; + fontflag[0] = true; + fontflag[1] = true; + fontflag[2] = true; init_ucs(conf, &ucsdata); } @@ -1636,7 +1666,8 @@ static void another_font(int fontno) { int basefont; int fw_dontcare, fw_bold, quality; - int c, u, w, x; + int c, w, x; + bool u; char *s; FontSpec *font; @@ -1659,7 +1690,7 @@ static void another_font(int fontno) c = font->charset; w = fw_dontcare; - u = FALSE; + u = false; s = font->name; x = font_width; @@ -1672,17 +1703,17 @@ static void another_font(int fontno) if (fontno & FONT_BOLD) w = fw_bold; if (fontno & FONT_UNDERLINE) - u = TRUE; + u = true; quality = conf_get_int(conf, CONF_font_quality); fonts[fontno] = CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, - FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS, + false, u, false, c, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), DEFAULT_PITCH | FF_DONTCARE, s); - fontflag[fontno] = 1; + fontflag[fontno] = true; } static void deinit_fonts(void) @@ -1692,15 +1723,15 @@ static void deinit_fonts(void) if (fonts[i]) DeleteObject(fonts[i]); fonts[i] = 0; - fontflag[i] = 0; + fontflag[i] = false; } } -void request_resize(void *frontend, int w, int h) +static void wintw_request_resize(TermWin *tw, int w, int h) { int width, height; - /* If the window is maximized supress resizing attempts */ + /* If the window is maximized suppress resizing attempts */ if (IsZoomed(hwnd)) { if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) return; @@ -1750,7 +1781,7 @@ void request_resize(void *frontend, int w, int h) } else reset_window(0); - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); } static void reset_window(int reinit) { @@ -1800,7 +1831,7 @@ static void reset_window(int reinit) { offset_height != (win_height-font_height*term->rows)/2) ){ offset_width = (win_width-font_width*term->cols)/2; offset_height = (win_height-font_height*term->rows)/2; - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -> Reposition terminal")); #endif @@ -1821,7 +1852,7 @@ static void reset_window(int reinit) { init_fonts(win_width/term->cols, win_height/term->rows); offset_width = (win_width-font_width*term->cols)/2; offset_height = (win_height-font_height*term->rows)/2; - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug((25, "reset_window() -> Z font resize to (%d, %d)", font_width, font_height)); @@ -1837,7 +1868,7 @@ static void reset_window(int reinit) { conf_get_int(conf, CONF_savelines)); offset_width = (win_width-font_width*term->cols)/2; offset_height = (win_height-font_height*term->rows)/2; - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -> Zoomed term_size")); #endif @@ -1871,7 +1902,7 @@ static void reset_window(int reinit) { SWP_NOMOVE | SWP_NOZORDER); } - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); return; } @@ -1930,7 +1961,7 @@ static void reset_window(int reinit) { font_height*term->rows + extra_height, SWP_NOMOVE | SWP_NOZORDER); - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug((27, "reset_window() -> window resize to (%d,%d)", font_width*term->cols + extra_width, @@ -1954,7 +1985,7 @@ static void reset_window(int reinit) { extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2; extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2; - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); #ifdef RDB_DEBUG_PATCH debug((25, "reset_window() -> font resize to (%d,%d)", font_width, font_height)); @@ -1972,12 +2003,13 @@ static void set_input_locale(HKL kl) kbd_codepage = atoi(lbuf); } -static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt) +static void click(Mouse_Button b, int x, int y, + bool shift, bool ctrl, bool alt) { int thistime = GetMessageTime(); if (send_raw_mouse && - !(shift && conf_get_int(conf, CONF_mouse_override))) { + !(shift && conf_get_bool(conf, CONF_mouse_override))) { lastbtn = MBT_NOTHING; term_mouse(term, b, translate_button(b), MA_CLICK, x, y, shift, ctrl, alt); @@ -2015,41 +2047,41 @@ static Mouse_Button translate_button(Mouse_Button button) return 0; /* shouldn't happen */ } -static void show_mouseptr(int show) +static void show_mouseptr(bool show) { /* NB that the counter in ShowCursor() is also frobbed by * update_mouse_pointer() */ - static int cursor_visible = 1; - if (!conf_get_int(conf, CONF_hide_mouseptr)) - show = 1; /* override if this feature disabled */ + static bool cursor_visible = true; + if (!conf_get_bool(conf, CONF_hide_mouseptr)) + show = true; /* override if this feature disabled */ if (cursor_visible && !show) - ShowCursor(FALSE); + ShowCursor(false); else if (!cursor_visible && show) - ShowCursor(TRUE); + ShowCursor(true); cursor_visible = show; } -static int is_alt_pressed(void) +static bool is_alt_pressed(void) { BYTE keystate[256]; int r = GetKeyboardState(keystate); if (!r) - return FALSE; + return false; if (keystate[VK_MENU] & 0x80) - return TRUE; + return true; if (keystate[VK_RMENU] & 0x80) - return TRUE; - return FALSE; + return true; + return false; } -static int resizing; +static bool resizing; -void notify_remote_exit(void *fe) +static void win_seat_notify_remote_exit(Seat *seat) { int exitcode, close_on_exit; if (!session_closed && - (exitcode = back->exitcode(backhandle)) >= 0) { + (exitcode = backend_exitcode(backend)) >= 0) { close_on_exit = conf_get_int(conf, CONF_close_on_exit); /* Abnormal exits will already have set session_closed and taken * appropriate action. */ @@ -2058,7 +2090,7 @@ void notify_remote_exit(void *fe) PostQuitMessage(0); } else { queue_toplevel_callback(close_session, NULL); - session_closed = TRUE; + session_closed = true; /* exitcode == INT_MAX indicates that the connection was closed * by a fatal error, so an error box will be coming our way and * we should not generate this informational one. */ @@ -2089,14 +2121,38 @@ static void conf_cache_data(void) vtmode = conf_get_int(conf, CONF_vtmode); } +static const int clips_system[] = { CLIP_SYSTEM }; + +static HDC make_hdc(void) +{ + HDC hdc; + + if (!hwnd) + return NULL; + + hdc = GetDC(hwnd); + if (!hdc) + return NULL; + + SelectPalette(hdc, pal, false); + return hdc; +} + +static void free_hdc(HDC hdc) +{ + assert(hwnd); + SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); + ReleaseDC(hwnd, hdc); +} + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; - static int ignore_clip = FALSE; - static int need_backend_resize = FALSE; - static int fullscr_on_max = FALSE; - static int processed_resize = FALSE; + static bool ignore_clip = false; + static bool need_backend_resize = false; + static bool fullscr_on_max = false; + static bool processed_resize = false; static UINT last_mousemove = 0; int resize_action; @@ -2117,9 +2173,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_CLOSE: { char *str; - show_mouseptr(1); + show_mouseptr(true); str = dupprintf("%s Exit Confirmation", appname); - if (session_closed || !conf_get_int(conf, CONF_warn_on_close) || + if (session_closed || !conf_get_bool(conf, CONF_warn_on_close) || MessageBox(hwnd, "Are you sure you want to close this session?", str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) @@ -2129,15 +2185,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } return 0; case WM_DESTROY: - show_mouseptr(1); + show_mouseptr(true); PostQuitMessage(0); return 0; case WM_INITMENUPOPUP: if ((HMENU)wParam == savedsess_menu) { /* About to pop up Saved Sessions sub-menu. * Refresh the session list. */ - get_sesslist(&sesslist, FALSE); /* free */ - get_sesslist(&sesslist, TRUE); + get_sesslist(&sesslist, false); /* free */ + get_sesslist(&sesslist, true); update_savedsess_menu(); return 0; } @@ -2155,7 +2211,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, char b[2048]; char *cl; const char *argprefix; - BOOL inherit_handles; + bool inherit_handles; STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE filemap = NULL; @@ -2171,14 +2227,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * config structure. */ SECURITY_ATTRIBUTES sa; + strbuf *serbuf; void *p; int size; - size = conf_serialised_size(conf); + serbuf = strbuf_new(); + conf_serialise(BinarySink_UPCAST(serbuf), conf); + size = serbuf->len; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = TRUE; + sa.bInheritHandle = true; filemap = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, @@ -2186,11 +2245,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (filemap && filemap != INVALID_HANDLE_VALUE) { p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size); if (p) { - conf_serialise(conf, p); + memcpy(p, serbuf->s, size); UnmapViewOfFile(p); } } - inherit_handles = TRUE; + + strbuf_free(serbuf); + inherit_handles = true; cl = dupprintf("putty %s&%p:%u", argprefix, filemap, (unsigned)size); } else if (wParam == IDM_SAVEDSESS) { @@ -2199,14 +2260,14 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (sessno < (unsigned)sesslist.nsessions) { const char *session = sesslist.sessions[sessno]; cl = dupprintf("putty %s@%s", argprefix, session); - inherit_handles = FALSE; + inherit_handles = false; } else break; } else /* IDM_NEWSESS */ { cl = dupprintf("putty%s%s", *argprefix ? " " : "", argprefix); - inherit_handles = FALSE; + inherit_handles = false; } GetModuleFileName(NULL, b, sizeof(b) - 1); @@ -2228,9 +2289,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } break; case IDM_RESTART: - if (!back) { - logevent(NULL, "----- Session restarted -----"); - term_pwron(term, FALSE); + if (!backend) { + lp_eventlog(default_logpolicy, + "----- Session restarted -----"); + term_pwron(term, false); start_backend(); } @@ -2239,12 +2301,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, { Conf *prev_conf; int init_lvl = 1; - int reconfig_result; + bool reconfig_result; if (reconfiguring) break; else - reconfiguring = TRUE; + reconfiguring = true; /* * Copy the current window title into the stored @@ -2257,8 +2319,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, prev_conf = conf_copy(conf); reconfig_result = - do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0); - reconfiguring = FALSE; + do_reconfig(hwnd, backend ? backend_cfg_info(backend) : 0); + reconfiguring = false; if (!reconfig_result) { conf_free(prev_conf); break; @@ -2301,10 +2363,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, /* Pass new config data to the terminal */ term_reconfig(term, conf); + setup_clipboards(term, conf); /* Pass new config data to the back end */ - if (back) - back->reconfig(backhandle, conf); + if (backend) + backend_reconfig(backend, conf); /* Screen size changed ? */ if (conf_get_int(conf, CONF_height) != @@ -2327,9 +2390,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, GetWindowLongPtr(hwnd, GWL_EXSTYLE); nexflag = exflag; - if (conf_get_int(conf, CONF_alwaysontop) != - conf_get_int(prev_conf, CONF_alwaysontop)) { - if (conf_get_int(conf, CONF_alwaysontop)) { + 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); @@ -2339,15 +2402,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, SWP_NOMOVE | SWP_NOSIZE); } } - if (conf_get_int(conf, CONF_sunken_edge)) + if (conf_get_bool(conf, CONF_sunken_edge)) nexflag |= WS_EX_CLIENTEDGE; else nexflag &= ~(WS_EX_CLIENTEDGE); nflg = flag; - if (conf_get_int(conf, is_full_screen() ? - CONF_scrollbar_in_fullscreen : - CONF_scrollbar)) + if (conf_get_bool(conf, is_full_screen() ? + CONF_scrollbar_in_fullscreen : + CONF_scrollbar)) nflg |= WS_VSCROLL; else nflg &= ~WS_VSCROLL; @@ -2384,10 +2447,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, init_lvl = 2; } - set_title(NULL, conf_get_str(conf, CONF_wintitle)); + win_set_title(wintw, conf_get_str(conf, CONF_wintitle)); if (IsIconic(hwnd)) { SetWindowText(hwnd, - conf_get_int(conf, CONF_win_name_always) ? + conf_get_bool(conf, CONF_win_name_always) ? window_name : icon_name); } @@ -2415,23 +2478,26 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, init_lvl = 2; } - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); reset_window(init_lvl); conf_free(prev_conf); } break; case IDM_COPYALL: - term_copyall(term); + term_copyall(term, clips_system, lenof(clips_system)); + break; + case IDM_COPY: + term_request_copy(term, clips_system, lenof(clips_system)); break; case IDM_PASTE: - request_paste(NULL); + term_request_paste(term, CLIP_SYSTEM); break; case IDM_CLRSB: term_clrsb(term); break; case IDM_RESET: - term_pwron(term, TRUE); + term_pwron(term, true); if (ldisc) ldisc_echoedit_update(ldisc); break; @@ -2446,7 +2512,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * We get this if the System menu has been activated * using the mouse. */ - show_mouseptr(1); + show_mouseptr(true); break; case SC_KEYMENU: /* @@ -2457,7 +2523,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * the menu up_ rather than just sitting there in * `ready to appear' state. */ - show_mouseptr(1); /* make sure pointer is visible */ + show_mouseptr(true); /* make sure pointer is visible */ if( lParam == 0 ) PostMessage(hwnd, WM_CHAR, ' ', 0); break; @@ -2477,8 +2543,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, */ if (i >= n_specials) break; - if (back) - back->special(backhandle, specials[i].code); + if (backend) + backend_special( + backend, specials[i].code, specials[i].arg); } } break; @@ -2499,7 +2566,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, (conf_get_int(conf, CONF_mouse_is_xterm) == 2))) { POINT cursorpos; - show_mouseptr(1); /* make sure pointer is visible */ + show_mouseptr(true); /* make sure pointer is visible */ GetCursorPos(&cursorpos); TrackPopupMenu(popup_menus[CTXMENU].menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, @@ -2508,43 +2575,45 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, break; } { - int button, press; + int button; + bool press; switch (message) { case WM_LBUTTONDOWN: button = MBT_LEFT; wParam |= MK_LBUTTON; - press = 1; + press = true; break; case WM_MBUTTONDOWN: button = MBT_MIDDLE; wParam |= MK_MBUTTON; - press = 1; + press = true; break; case WM_RBUTTONDOWN: button = MBT_RIGHT; wParam |= MK_RBUTTON; - press = 1; + press = true; break; case WM_LBUTTONUP: button = MBT_LEFT; wParam &= ~MK_LBUTTON; - press = 0; + press = false; break; case WM_MBUTTONUP: button = MBT_MIDDLE; wParam &= ~MK_MBUTTON; - press = 0; + press = false; break; case WM_RBUTTONUP: button = MBT_RIGHT; wParam &= ~MK_RBUTTON; - press = 0; + press = false; break; - default: - button = press = 0; /* shouldn't happen */ + default: /* shouldn't happen */ + button = 0; + press = false; } - show_mouseptr(1); + show_mouseptr(true); /* * Special case: in full-screen mode, if the left * button is clicked in the very top left corner of the @@ -2552,7 +2621,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * selection. */ { - char mouse_on_hotspot = 0; + bool mouse_on_hotspot = false; POINT pt; GetCursorPos(&pt); @@ -2569,13 +2638,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (mi.rcMonitor.left == pt.x && mi.rcMonitor.top == pt.y) { - mouse_on_hotspot = 1; + mouse_on_hotspot = true; } } } #else if (pt.x == 0 && pt.y == 0) { - mouse_on_hotspot = 1; + mouse_on_hotspot = true; } #endif if (is_full_screen() && press && @@ -2613,7 +2682,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static LPARAM lp = 0; if (wParam != wp || lParam != lp || last_mousemove != WM_MOUSEMOVE) { - show_mouseptr(1); + show_mouseptr(true); wp = wParam; lp = lParam; last_mousemove = WM_MOUSEMOVE; } @@ -2645,7 +2714,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, static LPARAM lp = 0; if (wParam != wp || lParam != lp || last_mousemove != WM_NCMOUSEMOVE) { - show_mouseptr(1); + show_mouseptr(true); wp = wParam; lp = lParam; last_mousemove = WM_NCMOUSEMOVE; } @@ -2657,8 +2726,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, break; case WM_DESTROYCLIPBOARD: if (!ignore_clip) - term_deselect(term); - ignore_clip = FALSE; + term_lost_clipboard_ownership(term, CLIP_SYSTEM); + ignore_clip = false; return 0; case WM_PAINT: { @@ -2667,7 +2736,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, HideCaret(hwnd); hdc = BeginPaint(hwnd, &p); if (pal) { - SelectPalette(hdc, pal, TRUE); + SelectPalette(hdc, pal, true); RealizePalette(hdc); } @@ -2703,12 +2772,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * current terminal appearance so that WM_PAINT becomes * completely trivial. However, this should do for now. */ - term_paint(term, hdc, + assert(!wintw_hdc); + wintw_hdc = hdc; + term_paint(term, (p.rcPaint.left-offset_width)/font_width, (p.rcPaint.top-offset_height)/font_height, (p.rcPaint.right-offset_width-1)/font_width, (p.rcPaint.bottom-offset_height-1)/font_height, !term->window_update_pending); + wintw_hdc = NULL; if (p.fErase || p.rcPaint.left < offset_width || @@ -2774,7 +2846,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } return 0; case WM_SETFOCUS: - term_set_focus(term, TRUE); + term_set_focus(term, true); CreateCaret(hwnd, caretbm, font_width, font_height); ShowCaret(hwnd); flash_window(0); /* stop */ @@ -2782,8 +2854,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term_update(term); break; case WM_KILLFOCUS: - show_mouseptr(1); - term_set_focus(term, FALSE); + show_mouseptr(true); + term_set_focus(term, false); DestroyCaret(); caret_x = caret_y = -1; /* ensure caret is replaced next time */ term_update(term); @@ -2792,13 +2864,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, #ifdef RDB_DEBUG_PATCH debug((27, "WM_ENTERSIZEMOVE")); #endif - EnableSizeTip(1); - resizing = TRUE; - need_backend_resize = FALSE; + EnableSizeTip(true); + resizing = true; + need_backend_resize = false; break; case WM_EXITSIZEMOVE: - EnableSizeTip(0); - resizing = FALSE; + EnableSizeTip(false); + resizing = false; #ifdef RDB_DEBUG_PATCH debug((27, "WM_EXITSIZEMOVE")); #endif @@ -2806,7 +2878,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term_size(term, conf_get_int(conf, CONF_height), conf_get_int(conf, CONF_width), conf_get_int(conf, CONF_savelines)); - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); } break; case WM_SIZING: @@ -2836,8 +2908,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, conf_set_int(conf, CONF_height, term->rows); conf_set_int(conf, CONF_width, term->cols); - InvalidateRect(hwnd, NULL, TRUE); - need_backend_resize = TRUE; + InvalidateRect(hwnd, NULL, true); + need_backend_resize = true; } width = r->right - r->left - extra_width; @@ -2903,7 +2975,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } /* break; (never reached) */ case WM_FULLSCR_ON_MAX: - fullscr_on_max = TRUE; + fullscr_on_max = true; break; case WM_MOVE: sys_cursor_update(); @@ -2921,12 +2993,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, #endif if (wParam == SIZE_MINIMIZED) SetWindowText(hwnd, - conf_get_int(conf, CONF_win_name_always) ? + conf_get_bool(conf, CONF_win_name_always) ? window_name : icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) SetWindowText(hwnd, window_name); if (wParam == SIZE_RESTORED) { - processed_resize = FALSE; + processed_resize = false; clear_full_screen(); if (processed_resize) { /* @@ -2939,8 +3011,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } } if (wParam == SIZE_MAXIMIZED && fullscr_on_max) { - fullscr_on_max = FALSE; - processed_resize = FALSE; + fullscr_on_max = false; + processed_resize = false; make_full_screen(); if (processed_resize) { /* @@ -2953,7 +3025,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } } - processed_resize = TRUE; + processed_resize = true; if (resize_action == RESIZE_DISABLED) { /* A resize, well it better be a minimize. */ @@ -2967,7 +3039,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, height = HIWORD(lParam); if (wParam == SIZE_MAXIMIZED && !was_zoomed) { - was_zoomed = 1; + was_zoomed = true; prev_rows = term->rows; prev_cols = term->cols; if (resize_action == RESIZE_TERM) { @@ -2984,7 +3056,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * arise in maximisation as well via the Aero * snap UI. */ - need_backend_resize = TRUE; + need_backend_resize = true; conf_set_int(conf, CONF_height, h); conf_set_int(conf, CONF_width, w); } else { @@ -2994,7 +3066,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } reset_window(0); } else if (wParam == SIZE_RESTORED && was_zoomed) { - was_zoomed = 0; + was_zoomed = false; if (resize_action == RESIZE_TERM) { w = (width-window_border*2) / font_width; if (w < 1) w = 1; @@ -3023,7 +3095,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * getting sent down the connection during an NT * opaque drag.) */ - need_backend_resize = TRUE; + need_backend_resize = true; conf_set_int(conf, CONF_height, h); conf_set_int(conf, CONF_width, w); } else { @@ -3075,25 +3147,25 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, break; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd && pal != NULL) { - HDC hdc = get_ctx(NULL); + HDC hdc = make_hdc(); if (hdc) { if (RealizePalette(hdc) > 0) UpdateColors(hdc); - free_ctx(hdc); + free_hdc(hdc); } } break; case WM_QUERYNEWPALETTE: if (pal != NULL) { - HDC hdc = get_ctx(NULL); + HDC hdc = make_hdc(); if (hdc) { if (RealizePalette(hdc) > 0) UpdateColors(hdc); - free_ctx(hdc); - return TRUE; + free_hdc(hdc); + return true; } } - return FALSE; + return false; case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: @@ -3141,8 +3213,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, */ term_seen_key_event(term); if (ldisc) - ldisc_send(ldisc, (char *)buf, len, 1); - show_mouseptr(0); + ldisc_send(ldisc, buf, len, true); + show_mouseptr(false); } } } @@ -3166,8 +3238,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, int n; char *buff; - if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || - osVersion.dwPlatformId == VER_PLATFORM_WIN32s) break; /* no Unicode */ + if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS || + osPlatformId == VER_PLATFORM_WIN32s) + break; /* no Unicode */ if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */ break; /* fall back to DefWindowProc */ @@ -3193,12 +3266,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (IS_HIGH_SURROGATE(hs) && i+2 < n) { WCHAR ls = *(unsigned short *)(buff+i+2); if (IS_LOW_SURROGATE(ls)) { - luni_send(ldisc, (unsigned short *)(buff+i), 2, 1); + luni_send(ldisc, (unsigned short *)(buff+i), + 2, true); i += 2; continue; } } - luni_send(ldisc, (unsigned short *)(buff+i), 1, 1); + luni_send(ldisc, (unsigned short *)(buff+i), 1, true); } } free(buff); @@ -3215,12 +3289,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, buf[0] = wParam >> 8; term_seen_key_event(term); if (ldisc) - lpage_send(ldisc, kbd_codepage, buf, 2, 1); + lpage_send(ldisc, kbd_codepage, buf, 2, true); } else { char c = (unsigned char) wParam; term_seen_key_event(term); if (ldisc) - lpage_send(ldisc, kbd_codepage, &c, 1, 1); + lpage_send(ldisc, kbd_codepage, &c, 1, true); } return (0); case WM_CHAR: @@ -3242,15 +3316,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, pair[0] = pending_surrogate; pair[1] = c; term_seen_key_event(term); - luni_send(ldisc, pair, 2, 1); + luni_send(ldisc, pair, 2, true); } else if (!IS_SURROGATE(c)) { term_seen_key_event(term); - luni_send(ldisc, &c, 1, 1); + luni_send(ldisc, &c, 1, true); } } return 0; case WM_SYSCOLORCHANGE: - if (conf_get_int(conf, CONF_system_colour)) { + if (conf_get_bool(conf, CONF_system_colour)) { /* Refresh palette from system colours. */ /* XXX actually this zaps the entire palette. */ systopalette(); @@ -3267,12 +3341,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } return 0; case WM_GOT_CLIPDATA: - if (process_clipdata((HGLOBAL)lParam, wParam)) - term_do_paste(term); + process_clipdata((HGLOBAL)lParam, wParam); return 0; default: if (message == wm_mousewheel || message == WM_MOUSEWHEEL) { - int shift_pressed=0, control_pressed=0; + bool shift_pressed = false, control_pressed = false; if (message == WM_MOUSEWHEEL) { wheel_accumulator += (short)HIWORD(wParam); @@ -3302,7 +3375,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, break; if (send_raw_mouse && - !(conf_get_int(conf, CONF_mouse_override) && + !(conf_get_bool(conf, CONF_mouse_override) && shift_pressed)) { /* Mouse wheel position is in screen coordinates for * some reason */ @@ -3340,7 +3413,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, * helper software tracks the system caret, so we should arrange to * have one.) */ -void sys_cursor(void *frontend, int x, int y) +static void wintw_set_cursor_pos(TermWin *tw, int x, int y) { int cx, cy; @@ -3373,10 +3446,10 @@ static void sys_cursor_update(void) SetCaretPos(caret_x, caret_y); /* IMM calls on Win98 and beyond only */ - if(osVersion.dwPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */ + if (osPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */ - if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && - osVersion.dwMinorVersion == 0) return; /* 95 */ + if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS && + osMinorVersion == 0) return; /* 95 */ /* we should have the IMM functions */ hIMC = ImmGetContext(hwnd); @@ -3394,19 +3467,20 @@ static void sys_cursor_update(void) * * We are allowed to fiddle with the contents of `text'. */ -void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) +static void do_text_internal( + int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) { COLORREF fg, bg, t; int nfg, nbg, nfont; - HDC hdc = ctx; RECT line_box; - int force_manual_underline = 0; + bool force_manual_underline = false; int fnt_width, char_width; int text_adjust = 0; int xoffset = 0; - int maxlen, remaining, opaque; - int is_cursor = FALSE; + int maxlen, remaining; + bool opaque; + bool is_cursor = false; static int *lpDx = NULL; static int lpDx_len = 0; int *lpDx_maybe; @@ -3429,10 +3503,11 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, y += offset_height; if ((attr & TATTR_ACTCURS) && (cursor_type == 0 || term->big_cursor)) { - attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS); + truecolour.fg = truecolour.bg = optionalrgb_none; + attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS|ATTR_DIM); /* cursor fg and bg */ attr |= (260 << ATTR_FGSHIFT) | (261 << ATTR_BGSHIFT); - is_cursor = TRUE; + is_cursor = true; } nfont = 0; @@ -3475,7 +3550,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, text[0] = ucsdata.unitab_xterm['q']; if (attr & ATTR_UNDER) { attr &= ~ATTR_UNDER; - force_manual_underline = 1; + force_manual_underline = true; } } #endif @@ -3501,7 +3576,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, another_font(nfont); if (!fonts[nfont]) { if (nfont & FONT_UNDERLINE) - force_manual_underline = 1; + force_manual_underline = true; /* Don't do the same for manual bold, it could be bad news. */ nfont &= ~(FONT_BOLD | FONT_UNDERLINE); @@ -3510,9 +3585,15 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, if (!fonts[nfont]) nfont = FONT_NORMAL; if (attr & ATTR_REVERSE) { + struct optionalrgb trgb; + t = nfg; nfg = nbg; nbg = t; + + trgb = truecolour.fg; + truecolour.fg = truecolour.bg; + truecolour.bg = trgb; } if (bold_colours && (attr & ATTR_BOLD) && !is_cursor) { if (nfg < 16) nfg |= 8; @@ -3522,15 +3603,29 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } - fg = colours[nfg]; - bg = colours[nbg]; - SelectObject(hdc, fonts[nfont]); - SetTextColor(hdc, fg); - SetBkColor(hdc, bg); + if (!pal && truecolour.fg.enabled) + fg = RGB(truecolour.fg.r, truecolour.fg.g, truecolour.fg.b); + else + fg = colours[nfg]; + + if (!pal && truecolour.bg.enabled) + bg = RGB(truecolour.bg.r, truecolour.bg.g, truecolour.bg.b); + else + bg = colours[nbg]; + + if (!pal && (attr & ATTR_DIM)) { + fg = RGB(GetRValue(fg) * 2 / 3, + GetGValue(fg) * 2 / 3, + GetBValue(fg) * 2 / 3); + } + + SelectObject(wintw_hdc, fonts[nfont]); + SetTextColor(wintw_hdc, fg); + SetBkColor(wintw_hdc, bg); if (attr & TATTR_COMBINING) - SetBkMode(hdc, TRANSPARENT); + SetBkMode(wintw_hdc, TRANSPARENT); else - SetBkMode(hdc, OPAQUE); + SetBkMode(wintw_hdc, OPAQUE); line_box.left = x; line_box.top = y; line_box.right = x + char_width * len; @@ -3567,7 +3662,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, * generally reasonable results. */ xoffset = char_width / 2; - SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); + SetTextAlign(wintw_hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); lpDx_maybe = NULL; maxlen = 1; } else { @@ -3576,12 +3671,12 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, * in the normal way. */ xoffset = 0; - SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + SetTextAlign(wintw_hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); lpDx_maybe = lpDx; maxlen = len; } - opaque = TRUE; /* start by erasing the rectangle */ + opaque = true; /* start by erasing the rectangle */ for (remaining = len; remaining > 0; text += len, remaining -= len, x += char_width * len2) { len = (maxlen < remaining ? maxlen : remaining); @@ -3663,14 +3758,14 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, if (nlen <= 0) return; /* Eeek! */ - ExtTextOutW(hdc, x + xoffset, + ExtTextOutW(wintw_hdc, x + xoffset, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), &line_box, uni_buf, nlen, lpDx_maybe); if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(hdc, TRANSPARENT); - ExtTextOutW(hdc, x + xoffset - 1, + SetBkMode(wintw_hdc, TRANSPARENT); + ExtTextOutW(wintw_hdc, x + xoffset - 1, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED, &line_box, uni_buf, nlen, lpDx_maybe); @@ -3689,12 +3784,12 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, for (i = 0; i < len; i++) directbuf[i] = text[i] & 0xFF; - ExtTextOut(hdc, x + xoffset, + ExtTextOut(wintw_hdc, x + xoffset, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), &line_box, directbuf, len, lpDx_maybe); if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(hdc, TRANSPARENT); + SetBkMode(wintw_hdc, TRANSPARENT); /* GRR: This draws the character outside its box and * can leave 'droppings' even with the clip box! I @@ -3705,7 +3800,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, * or -1 for this shift depending on if the leftmost * column is blank... */ - ExtTextOut(hdc, x + xoffset - 1, + ExtTextOut(wintw_hdc, x + xoffset - 1, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED, &line_box, directbuf, len, lpDx_maybe); @@ -3726,15 +3821,15 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, wbuf[i] = text[i]; /* print Glyphs as they are, without Windows' Shaping*/ - general_textout(hdc, x + xoffset, + general_textout(wintw_hdc, x + xoffset, y - font_height * (lattr==LATTR_BOT) + text_adjust, &line_box, wbuf, len, lpDx, opaque && !(attr & TATTR_COMBINING)); /* And the shadow bold hack. */ if (bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(hdc, TRANSPARENT); - ExtTextOutW(hdc, x + xoffset - 1, + SetBkMode(wintw_hdc, TRANSPARENT); + ExtTextOutW(wintw_hdc, x + xoffset - 1, y - font_height * (lattr == LATTR_BOT) + text_adjust, ETO_CLIPPED, &line_box, wbuf, len, lpDx_maybe); @@ -3745,8 +3840,8 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, * If we're looping round again, stop erasing the background * rectangle. */ - SetBkMode(hdc, TRANSPARENT); - opaque = FALSE; + SetBkMode(wintw_hdc, TRANSPARENT); + opaque = false; } if (lattr != LATTR_TOP && (force_manual_underline || (und_mode == UND_LINE @@ -3756,10 +3851,10 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, if (lattr == LATTR_BOT) dec = dec * 2 - font_height; - oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, fg)); - MoveToEx(hdc, line_box.left, line_box.top + dec, NULL); - LineTo(hdc, line_box.right, line_box.top + dec); - oldpen = SelectObject(hdc, oldpen); + oldpen = SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, fg)); + MoveToEx(wintw_hdc, line_box.left, line_box.top + dec, NULL); + LineTo(wintw_hdc, line_box.right, line_box.top + dec); + oldpen = SelectObject(wintw_hdc, oldpen); DeleteObject(oldpen); } } @@ -3767,8 +3862,9 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, /* * Wrapper that handles combining characters. */ -void do_text(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) +static void wintw_draw_text( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) { if (attr & TATTR_COMBINING) { unsigned long a = 0; @@ -3778,13 +3874,13 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, len0 = 2; if (len-len0 >= 1 && IS_LOW_VARSEL(text[len0])) { attr &= ~TATTR_COMBINING; - do_text_internal(ctx, x, y, text, len0+1, attr, lattr); + do_text_internal(x, y, text, len0+1, attr, lattr, truecolour); text += len0+1; len -= len0+1; a = TATTR_COMBINING; } else if (len-len0 >= 2 && IS_HIGH_VARSEL(text[len0], text[len0+1])) { attr &= ~TATTR_COMBINING; - do_text_internal(ctx, x, y, text, len0+2, attr, lattr); + do_text_internal(x, y, text, len0+2, attr, lattr, truecolour); text += len0+2; len -= len0+2; a = TATTR_COMBINING; @@ -3794,34 +3890,32 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, while (len--) { if (len >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) { - do_text_internal(ctx, x, y, text, 2, attr | a, lattr); + do_text_internal(x, y, text, 2, attr | a, lattr, truecolour); len--; text++; - } else { - do_text_internal(ctx, x, y, text, 1, attr | a, lattr); - } + } else + do_text_internal(x, y, text, 1, attr | a, lattr, truecolour); text++; a = TATTR_COMBINING; } } else - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(x, y, text, len, attr, lattr, truecolour); } -void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) +static void wintw_draw_cursor( + TermWin *tw, int x, int y, wchar_t *text, int len, + unsigned long attr, int lattr, truecolour truecolour) { - int fnt_width; int char_width; - HDC hdc = ctx; int ctype = cursor_type; lattr &= LATTR_MODE; if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) { if (*text != UCSWIDE) { - do_text(ctx, x, y, text, len, attr, lattr); + win_draw_text(tw, x, y, text, len, attr, lattr, truecolour); return; } ctype = 2; @@ -3843,9 +3937,9 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, pts[2].x = pts[3].x = x + char_width - 1; pts[0].y = pts[3].y = pts[4].y = y; pts[1].y = pts[2].y = y + font_height - 1; - oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261])); - Polyline(hdc, pts, 5); - oldpen = SelectObject(hdc, oldpen); + oldpen = SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colours[261])); + Polyline(wintw_hdc, pts, 5); + oldpen = SelectObject(wintw_hdc, oldpen); DeleteObject(oldpen); } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) { int startx, starty, dx, dy, length, i; @@ -3868,15 +3962,15 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, if (attr & TATTR_ACTCURS) { HPEN oldpen; oldpen = - SelectObject(hdc, CreatePen(PS_SOLID, 0, colours[261])); - MoveToEx(hdc, startx, starty, NULL); - LineTo(hdc, startx + dx * length, starty + dy * length); - oldpen = SelectObject(hdc, oldpen); + SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colours[261])); + MoveToEx(wintw_hdc, startx, starty, NULL); + LineTo(wintw_hdc, startx + dx * length, starty + dy * length); + oldpen = SelectObject(wintw_hdc, oldpen); DeleteObject(oldpen); } else { for (i = 0; i < length; i++) { if (i % 2 == 0) { - SetPixel(hdc, startx, starty, colours[261]); + SetPixel(wintw_hdc, startx, starty, colours[261]); } startx += dx; starty += dy; @@ -3887,8 +3981,8 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, /* This function gets the actual width of a character in the normal font. */ -int char_width(Context ctx, int uc) { - HDC hdc = ctx; +static int wintw_char_width(TermWin *tw, int uc) +{ int ibuf = 0; /* If the font max is the same as the font ave width then this @@ -3915,26 +4009,28 @@ int char_width(Context ctx, int uc) { return 1; if ( (uc & CSET_MASK) == CSET_ACP ) { - SelectObject(hdc, fonts[FONT_NORMAL]); + SelectObject(wintw_hdc, fonts[FONT_NORMAL]); } else if ( (uc & CSET_MASK) == CSET_OEMCP ) { another_font(FONT_OEM); if (!fonts[FONT_OEM]) return 0; - SelectObject(hdc, fonts[FONT_OEM]); + SelectObject(wintw_hdc, fonts[FONT_OEM]); } else return 0; - if ( GetCharWidth32(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1 && - GetCharWidth(hdc, uc&~CSET_MASK, uc&~CSET_MASK, &ibuf) != 1) + if (GetCharWidth32(wintw_hdc, uc & ~CSET_MASK, + uc & ~CSET_MASK, &ibuf) != 1 && + GetCharWidth(wintw_hdc, uc & ~CSET_MASK, + uc & ~CSET_MASK, &ibuf) != 1) return 0; } else { /* Speedup, I know of no font where ascii is the wrong width */ if (uc >= ' ' && uc <= '~') return 1; - SelectObject(hdc, fonts[FONT_NORMAL]); - if ( GetCharWidth32W(hdc, uc, uc, &ibuf) == 1 ) + SelectObject(wintw_hdc, fonts[FONT_NORMAL]); + if (GetCharWidth32W(wintw_hdc, uc, uc, &ibuf) == 1) /* Okay that one worked */ ; - else if ( GetCharWidthW(hdc, uc, uc, &ibuf) == 1 ) + else if (GetCharWidthW(wintw_hdc, uc, uc, &ibuf) == 1) /* This should work on 9x too, but it's "less accurate" */ ; else return 0; @@ -3949,12 +4045,15 @@ int char_width(Context ctx, int uc) { DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO)); DECL_WINDOWS_FUNCTION(static, BOOL, ToUnicodeEx, (UINT, UINT, const BYTE *, LPWSTR, int, UINT, HKL)); +DECL_WINDOWS_FUNCTION(static, BOOL, PlaySound, (LPCTSTR, HMODULE, DWORD)); static void init_winfuncs(void) { HMODULE user32_module = load_system32_dll("user32.dll"); + HMODULE winmm_module = load_system32_dll("winmm.dll"); GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx); + GET_WINDOWS_FUNCTION_PP(winmm_module, PlaySound); } /* @@ -3967,14 +4066,15 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output) { BYTE keystate[256]; - int scan, left_alt = 0, key_down, shift_state; + int scan, shift_state; + bool left_alt = false, key_down; int r, i, code; unsigned char *p = output; static int alt_sum = 0; int funky_type = conf_get_int(conf, CONF_funky_type); - int no_applic_k = conf_get_int(conf, CONF_no_applic_k); - int ctrlaltkeys = conf_get_int(conf, CONF_ctrlaltkeys); - int nethack_keypad = conf_get_int(conf, CONF_nethack_keypad); + bool no_applic_k = conf_get_bool(conf, CONF_no_applic_k); + bool ctrlaltkeys = conf_get_bool(conf, CONF_ctrlaltkeys); + bool nethack_keypad = conf_get_bool(conf, CONF_nethack_keypad); HKL kbd_layout = GetKeyboardLayout(0); @@ -4089,7 +4189,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return 0; if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0) - left_alt = 1; + left_alt = true; key_down = ((HIWORD(lParam) & KF_UP) == 0); @@ -4099,7 +4199,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, keystate[VK_MENU] = 0; else { keystate[VK_RMENU] = 0x80; - left_alt = 0; + left_alt = false; } } @@ -4110,7 +4210,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, /* Note if AltGr was pressed and if it was used as a compose key */ if (!compose_state) { compose_keycode = 0x100; - if (conf_get_int(conf, CONF_compose_key)) { + if (conf_get_bool(conf, CONF_compose_key)) { if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) compose_keycode = wParam; } @@ -4192,6 +4292,15 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0); return 0; } + if (wParam == VK_PRIOR && shift_state == 3) { /* ctrl-shift-pageup */ + SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0); + return 0; + } + if (wParam == VK_NEXT && shift_state == 3) { /* ctrl-shift-pagedown */ + SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0); + return 0; + } + if (wParam == VK_PRIOR && shift_state == 2) { SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); return 0; @@ -4208,20 +4317,66 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, term_scroll_to_selection(term, (wParam == VK_PRIOR ? 0 : 1)); return 0; } + if (wParam == VK_INSERT && shift_state == 2) { + switch (conf_get_int(conf, CONF_ctrlshiftins)) { + case CLIPUI_IMPLICIT: + break; /* no need to re-copy to CLIP_LOCAL */ + case CLIPUI_EXPLICIT: + term_request_copy(term, clips_system, lenof(clips_system)); + break; + default: + break; + } + return 0; + } if (wParam == VK_INSERT && shift_state == 1) { - request_paste(NULL); + switch (conf_get_int(conf, CONF_ctrlshiftins)) { + case CLIPUI_IMPLICIT: + term_request_paste(term, CLIP_LOCAL); + break; + case CLIPUI_EXPLICIT: + term_request_paste(term, CLIP_SYSTEM); + break; + default: + break; + } return 0; } - if (left_alt && wParam == VK_F4 && conf_get_int(conf, CONF_alt_f4)) { + if (wParam == 'C' && shift_state == 3) { + switch (conf_get_int(conf, CONF_ctrlshiftcv)) { + case CLIPUI_IMPLICIT: + break; /* no need to re-copy to CLIP_LOCAL */ + case CLIPUI_EXPLICIT: + term_request_copy(term, clips_system, lenof(clips_system)); + break; + default: + break; + } + return 0; + } + if (wParam == 'V' && shift_state == 3) { + switch (conf_get_int(conf, CONF_ctrlshiftcv)) { + case CLIPUI_IMPLICIT: + term_request_paste(term, CLIP_LOCAL); + break; + case CLIPUI_EXPLICIT: + term_request_paste(term, CLIP_SYSTEM); + break; + default: + break; + } + return 0; + } + if (left_alt && wParam == VK_F4 && conf_get_bool(conf, CONF_alt_f4)) { return -1; } - if (left_alt && wParam == VK_SPACE && conf_get_int(conf, - CONF_alt_space)) { + if (left_alt && wParam == VK_SPACE && conf_get_bool(conf, + CONF_alt_space)) { SendMessage(hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); return -1; } if (left_alt && wParam == VK_RETURN && - conf_get_int(conf, CONF_fullscreenonaltenter) && + conf_get_bool(conf, CONF_fullscreenonaltenter) && (conf_get_int(conf, CONF_resize_action) != RESIZE_DISABLED)) { if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT) flip_full_screen(); @@ -4229,7 +4384,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } /* Control-Numlock for app-keypad mode switch */ if (wParam == VK_PAUSE && shift_state == 2) { - term->app_keypad_keys ^= 1; + term->app_keypad_keys = !term->app_keypad_keys; return 0; } @@ -4365,13 +4520,13 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, } if (wParam == VK_BACK && shift_state == 0) { /* Backspace */ - *p++ = (conf_get_int(conf, CONF_bksp_is_delete) ? 0x7F : 0x08); + *p++ = (conf_get_bool(conf, CONF_bksp_is_delete) ? 0x7F : 0x08); *p++ = 0; return -2; } if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */ /* We do the opposite of what is configured */ - *p++ = (conf_get_int(conf, CONF_bksp_is_delete) ? 0x08 : 0x7F); + *p++ = (conf_get_bool(conf, CONF_bksp_is_delete) ? 0x08 : 0x7F); *p++ = 0; return -2; } @@ -4390,8 +4545,8 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return p - output; } if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */ - if (back) - back->special(backhandle, TS_BRK); + if (backend) + backend_special(backend, SS_BRK, 0); return 0; } if (wParam == VK_PAUSE) { /* Break/Pause */ @@ -4581,7 +4736,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return p - output; } if ((code == 1 || code == 4) && - conf_get_int(conf, CONF_rxvt_homeend)) { + conf_get_bool(conf, CONF_rxvt_homeend)) { p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1BOw"); return p - output; } @@ -4638,11 +4793,11 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, /* Okay we've done everything interesting; let windows deal with * the boring stuff */ { - BOOL capsOn=0; + bool capsOn = false; /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */ if(keystate[VK_CAPITAL] != 0 && - conf_get_int(conf, CONF_xlat_capslockcyr)) { + conf_get_bool(conf, CONF_xlat_capslockcyr)) { capsOn= !left_alt; keystate[VK_CAPITAL] = 0; } @@ -4650,7 +4805,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, /* XXX how do we know what the max size of the keys array should * be is? There's indication on MS' website of an Inquire/InquireEx * functioning returning a KBINFO structure which tells us. */ - if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT && p_ToUnicodeEx) { + if (osPlatformId == VER_PLATFORM_WIN32_NT && p_ToUnicodeEx) { r = p_ToUnicodeEx(wParam, scan, keystate, keys_unicode, lenof(keys_unicode), 0, kbd_layout); } else { @@ -4718,7 +4873,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, keybuf = nc; term_seen_key_event(term); if (ldisc) - luni_send(ldisc, &keybuf, 1, 1); + luni_send(ldisc, &keybuf, 1, true); continue; } @@ -4730,7 +4885,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, keybuf = alt_sum; term_seen_key_event(term); if (ldisc) - luni_send(ldisc, &keybuf, 1, 1); + luni_send(ldisc, &keybuf, 1, true); } else { char ch = (char) alt_sum; /* @@ -4744,13 +4899,13 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, */ term_seen_key_event(term); if (ldisc) - ldisc_send(ldisc, &ch, 1, 1); + ldisc_send(ldisc, &ch, 1, true); } alt_sum = 0; } else { term_seen_key_event(term); if (ldisc) - luni_send(ldisc, &wch, 1, 1); + luni_send(ldisc, &wch, 1, true); } } else { if(capsOn && wch < 0x80) { @@ -4759,17 +4914,19 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, cbuf[1] = xlat_uskbd2cyrllic(wch); term_seen_key_event(term); if (ldisc) - luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, 1); + luni_send(ldisc, cbuf+!left_alt, 1+!!left_alt, + true); } else { WCHAR cbuf[2]; cbuf[0] = '\033'; cbuf[1] = wch; term_seen_key_event(term); if (ldisc) - luni_send(ldisc, cbuf +!left_alt, 1+!!left_alt, 1); + luni_send(ldisc, cbuf +!left_alt, 1+!!left_alt, + true); } } - show_mouseptr(0); + show_mouseptr(false); } /* This is so the ALT-Numpad and dead keys work correctly. */ @@ -4777,7 +4934,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return p - output; } - /* If we're definitly not building up an ALT-54321 then clear it */ + /* If we're definitely not building up an ALT-54321 then clear it */ if (!left_alt) keys_unicode[0] = 0; /* If we will be using alt_sum fix the 256s */ @@ -4792,36 +4949,36 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, * we return -1, which means Windows will give the keystroke * its default handling (i.e. bring up the System menu). */ - if (wParam == VK_MENU && !conf_get_int(conf, CONF_alt_only)) + if (wParam == VK_MENU && !conf_get_bool(conf, CONF_alt_only)) return 0; return -1; } -void set_title(void *frontend, char *title) +static void wintw_set_title(TermWin *tw, const char *title) { sfree(window_name); window_name = snewn(1 + strlen(title), char); strcpy(window_name, title); - if (conf_get_int(conf, CONF_win_name_always) || !IsIconic(hwnd)) + if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(hwnd)) SetWindowText(hwnd, title); } -void set_icon(void *frontend, char *title) +static void wintw_set_icon_title(TermWin *tw, const char *title) { sfree(icon_name); icon_name = snewn(1 + strlen(title), char); strcpy(icon_name, title); - if (!conf_get_int(conf, CONF_win_name_always) && IsIconic(hwnd)) + if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(hwnd)) SetWindowText(hwnd, title); } -void set_sbar(void *frontend, int total, int start, int page) +static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) { SCROLLINFO si; - if (!conf_get_int(conf, is_full_screen() ? - CONF_scrollbar_in_fullscreen : CONF_scrollbar)) + if (!conf_get_bool(conf, is_full_screen() ? + CONF_scrollbar_in_fullscreen : CONF_scrollbar)) return; si.cbSize = sizeof(si); @@ -4831,41 +4988,46 @@ void set_sbar(void *frontend, int total, int start, int page) si.nPage = page; si.nPos = start; if (hwnd) - SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + SetScrollInfo(hwnd, SB_VERT, &si, true); } -Context get_ctx(void *frontend) +static bool wintw_setup_draw_ctx(TermWin *tw) { - HDC hdc; - if (hwnd) { - hdc = GetDC(hwnd); - if (hdc && pal) - SelectPalette(hdc, pal, FALSE); - return hdc; - } else - return NULL; + assert(!wintw_hdc); + wintw_hdc = make_hdc(); + return wintw_hdc != NULL; } -void free_ctx(Context ctx) +static void wintw_free_draw_ctx(TermWin *tw) { - SelectPalette(ctx, GetStockObject(DEFAULT_PALETTE), FALSE); - ReleaseDC(hwnd, ctx); + assert(wintw_hdc); + free_hdc(wintw_hdc); + wintw_hdc = NULL; } static void real_palette_set(int n, int r, int g, int b) { + internal_set_colour(n, r, g, b); if (pal) { logpal->palPalEntry[n].peRed = r; logpal->palPalEntry[n].peGreen = g; logpal->palPalEntry[n].peBlue = b; logpal->palPalEntry[n].peFlags = PC_NOCOLLAPSE; - colours[n] = PALETTERGB(r, g, b); SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry); - } else - colours[n] = RGB(r, g, b); + } } -void palette_set(void *frontend, int n, int r, int g, int b) +static bool wintw_palette_get(TermWin *tw, int n, int *r, int *g, int *b) +{ + if (n < 0 || n >= NALLCOLOURS) + return false; + *r = colours_rgb[n].r; + *g = colours_rgb[n].g; + *b = colours_rgb[n].b; + return true; +} + +static void wintw_palette_set(TermWin *tw, int n, int r, int g, int b) { if (n >= 16) n += 256 - 16; @@ -4873,56 +5035,56 @@ void palette_set(void *frontend, int n, int r, int g, int b) return; real_palette_set(n, r, g, b); if (pal) { - HDC hdc = get_ctx(frontend); + HDC hdc = make_hdc(); UnrealizeObject(pal); RealizePalette(hdc); - free_ctx(hdc); + free_hdc(hdc); } else { if (n == (ATTR_DEFBG>>ATTR_BGSHIFT)) /* If Default Background changes, we need to ensure any * space between the text area and the window border is * redrawn. */ - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); } } -void palette_reset(void *frontend) +static void wintw_palette_reset(TermWin *tw) { int i; /* And this */ for (i = 0; i < NALLCOLOURS; i++) { + internal_set_colour(i, defpal[i].rgbtRed, + defpal[i].rgbtGreen, defpal[i].rgbtBlue); if (pal) { logpal->palPalEntry[i].peRed = defpal[i].rgbtRed; logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen; logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue; logpal->palPalEntry[i].peFlags = 0; - colours[i] = PALETTERGB(defpal[i].rgbtRed, - defpal[i].rgbtGreen, - defpal[i].rgbtBlue); - } else - colours[i] = RGB(defpal[i].rgbtRed, - defpal[i].rgbtGreen, defpal[i].rgbtBlue); + } } if (pal) { HDC hdc; SetPaletteEntries(pal, 0, NALLCOLOURS, logpal->palPalEntry); - hdc = get_ctx(frontend); + hdc = make_hdc(); RealizePalette(hdc); - free_ctx(hdc); + free_hdc(hdc); } else { /* Default Background may have changed. Ensure any space between * text area and window border is redrawn. */ - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); } } -void write_aclip(void *frontend, char *data, int len, int must_deselect) +void write_aclip(int clipboard, char *data, int len, bool must_deselect) { HGLOBAL clipdata; void *lock; + if (clipboard != CLIP_SYSTEM) + return; + clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1); if (!clipdata) return; @@ -4934,7 +5096,7 @@ void write_aclip(void *frontend, char *data, int len, int must_deselect) GlobalUnlock(clipdata); if (!must_deselect) - SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0); + SendMessage(hwnd, WM_IGNORE_CLIP, true, 0); if (OpenClipboard(hwnd)) { EmptyClipboard(); @@ -4944,18 +5106,35 @@ void write_aclip(void *frontend, char *data, int len, int must_deselect) GlobalFree(clipdata); if (!must_deselect) - SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0); + SendMessage(hwnd, WM_IGNORE_CLIP, false, 0); +} + +typedef struct _rgbindex { + int index; + COLORREF ref; +} rgbindex; + +int cmpCOLORREF(void *va, void *vb) +{ + COLORREF a = ((rgbindex *)va)->ref; + COLORREF b = ((rgbindex *)vb)->ref; + return (a < b) ? -1 : (a > b) ? +1 : 0; } /* * Note: unlike write_aclip() this will not append a nul. */ -void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect) +static void wintw_clip_write( + TermWin *tw, int clipboard, wchar_t *data, int *attr, + truecolour *truecolour, int len, bool must_deselect) { HGLOBAL clipdata, clipdata2, clipdata3; int len2; void *lock, *lock2, *lock3; + if (clipboard != CLIP_SYSTEM) + return; + len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL); clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, @@ -4984,7 +5163,7 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des memcpy(lock, data, len * sizeof(wchar_t)); WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL); - if (conf_get_int(conf, CONF_rtf_paste)) { + if (conf_get_bool(conf, CONF_rtf_paste)) { wchar_t unitab[256]; char *rtf = NULL; unsigned char *tdata = (unsigned char *)lock2; @@ -4993,12 +5172,15 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des int rtfsize = 0; int multilen, blen, alen, totallen, i; char before[16], after[4]; - int fgcolour, lastfgcolour = 0; - int bgcolour, lastbgcolour = 0; + int fgcolour, lastfgcolour = -1; + int bgcolour, lastbgcolour = -1; + COLORREF fg, lastfg = -1; + COLORREF bg, lastbg = -1; int attrBold, lastAttrBold = 0; int attrUnder, lastAttrUnder = 0; int palette[NALLCOLOURS]; int numcolours; + tree234 *rgbtree = NULL; FontSpec *font = conf_get_fontspec(conf, CONF_font); get_unitab(CP_ACP, unitab, 0); @@ -5036,7 +5218,7 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des fgcolour ++; } - if (attr[i] & ATTR_BLINK) { + if ((attr[i] & ATTR_BLINK)) { if (bgcolour < 8) /* ANSI colours */ bgcolour += 8; else if (bgcolour >= 256) /* Default colours */ @@ -5047,6 +5229,28 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des palette[bgcolour]++; } + if (truecolour) { + rgbtree = newtree234(cmpCOLORREF); + for (i = 0; i < (len-1); i++) { + if (truecolour[i].fg.enabled) { + rgbindex *rgbp = snew(rgbindex); + rgbp->ref = RGB(truecolour[i].fg.r, + truecolour[i].fg.g, + truecolour[i].fg.b); + if (add234(rgbtree, rgbp) != rgbp) + sfree(rgbp); + } + if (truecolour[i].bg.enabled) { + rgbindex *rgbp = snew(rgbindex); + rgbp->ref = RGB(truecolour[i].bg.r, + truecolour[i].bg.g, + truecolour[i].bg.b); + if (add234(rgbtree, rgbp) != rgbp) + sfree(rgbp); + } + } + } + /* * Next - Create a reduced palette */ @@ -5056,6 +5260,12 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des palette[i] = ++numcolours; } + if (rgbtree) { + rgbindex *rgbp; + for (i = 0; (rgbp = index234(rgbtree, i)) != NULL; i++) + rgbp->index = ++numcolours; + } + /* * Finally - Write the colour table */ @@ -5068,6 +5278,12 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue); } } + if (rgbtree) { + rgbindex *rgbp; + for (i = 0; (rgbp = index234(rgbtree, i)) != NULL; i++) + rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", + GetRValue(rgbp->ref), GetGValue(rgbp->ref), GetBValue(rgbp->ref)); + } strcpy(&rtf[rtflen], "}"); rtflen ++; } @@ -5111,23 +5327,44 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des /* * Determine foreground and background colours */ - fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT); - bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT); + if (truecolour && truecolour[tindex].fg.enabled) { + fgcolour = -1; + fg = RGB(truecolour[tindex].fg.r, + truecolour[tindex].fg.g, + truecolour[tindex].fg.b); + } else { + fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT); + fg = -1; + } + + if (truecolour && truecolour[tindex].bg.enabled) { + bgcolour = -1; + bg = RGB(truecolour[tindex].bg.r, + truecolour[tindex].bg.g, + truecolour[tindex].bg.b); + } else { + bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT); + bg = -1; + } if (attr[tindex] & ATTR_REVERSE) { int tmpcolour = fgcolour; /* Swap foreground and background */ fgcolour = bgcolour; bgcolour = tmpcolour; + + COLORREF tmpref = fg; + fg = bg; + bg = tmpref; } - if (bold_colours && (attr[tindex] & ATTR_BOLD)) { + if (bold_colours && (attr[tindex] & ATTR_BOLD) && (fgcolour >= 0)) { if (fgcolour < 8) /* ANSI colours */ fgcolour += 8; else if (fgcolour >= 256) /* Default colours */ fgcolour ++; } - if (attr[tindex] & ATTR_BLINK) { + if ((attr[tindex] & ATTR_BLINK) && (bgcolour >= 0)) { if (bgcolour < 8) /* ANSI colours */ bgcolour += 8; else if (bgcolour >= 256) /* Default colours */ @@ -5166,15 +5403,33 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des /* * Write RTF text attributes */ - if (lastfgcolour != fgcolour) { - lastfgcolour = fgcolour; - rtflen += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0); - } + if ((lastfgcolour != fgcolour) || (lastfg != fg)) { + lastfgcolour = fgcolour; + lastfg = fg; + if (fg == -1) + rtflen += sprintf(&rtf[rtflen], "\\cf%d ", + (fgcolour >= 0) ? palette[fgcolour] : 0); + else { + rgbindex rgb, *rgbp; + rgb.ref = fg; + if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) + rtflen += sprintf(&rtf[rtflen], "\\cf%d ", rgbp->index); + } + } - if (lastbgcolour != bgcolour) { - lastbgcolour = bgcolour; - rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0); - } + if ((lastbgcolour != bgcolour) || (lastbg != bg)) { + lastbgcolour = bgcolour; + lastbg = bg; + if (bg == -1) + rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", + (bgcolour >= 0) ? palette[bgcolour] : 0); + else { + rgbindex rgb, *rgbp; + rgb.ref = bg; + if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) + rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", rgbp->index); + } + } if (lastAttrBold != attrBold) { lastAttrBold = attrBold; @@ -5196,8 +5451,8 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1, NULL, 0, NULL, NULL); if (multilen != 1) { - blen = sprintf(before, "{\\uc%d\\u%d", multilen, - udata[uindex]); + blen = sprintf(before, "{\\uc%d\\u%d", (int)multilen, + (int)udata[uindex]); alen = 1; strcpy(after, "}"); } else { blen = sprintf(before, "\\u%d", udata[uindex]); @@ -5255,6 +5510,13 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des GlobalUnlock(clipdata3); } sfree(rtf); + + if (rgbtree) { + rgbindex *rgbp; + while ((rgbp = delpos234(rgbtree, 0)) != NULL) + sfree(rgbp); + freetree234(rgbtree); + } } else clipdata3 = NULL; @@ -5262,7 +5524,7 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des GlobalUnlock(clipdata2); if (!must_deselect) - SendMessage(hwnd, WM_IGNORE_CLIP, TRUE, 0); + SendMessage(hwnd, WM_IGNORE_CLIP, true, 0); if (OpenClipboard(hwnd)) { EmptyClipboard(); @@ -5277,7 +5539,7 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des } if (!must_deselect) - SendMessage(hwnd, WM_IGNORE_CLIP, FALSE, 0); + SendMessage(hwnd, WM_IGNORE_CLIP, false, 0); } static DWORD WINAPI clipboard_read_threadfunc(void *param) @@ -5287,9 +5549,11 @@ static DWORD WINAPI clipboard_read_threadfunc(void *param) if (OpenClipboard(NULL)) { if ((clipdata = GetClipboardData(CF_UNICODETEXT))) { - SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)1, (LPARAM)clipdata); + SendMessage(hwnd, WM_GOT_CLIPDATA, + (WPARAM)true, (LPARAM)clipdata); } else if ((clipdata = GetClipboardData(CF_TEXT))) { - SendMessage(hwnd, WM_GOT_CLIPDATA, (WPARAM)0, (LPARAM)clipdata); + SendMessage(hwnd, WM_GOT_CLIPDATA, + (WPARAM)false, (LPARAM)clipdata); } CloseClipboard(); } @@ -5297,11 +5561,10 @@ static DWORD WINAPI clipboard_read_threadfunc(void *param) return 0; } -static int process_clipdata(HGLOBAL clipdata, int unicode) +static void process_clipdata(HGLOBAL clipdata, bool unicode) { - sfree(clipboard_contents); - clipboard_contents = NULL; - clipboard_length = 0; + wchar_t *clipboard_contents = NULL; + size_t clipboard_length = 0; if (unicode) { wchar_t *p = GlobalLock(clipdata); @@ -5314,7 +5577,7 @@ static int process_clipdata(HGLOBAL clipdata, int unicode) clipboard_contents = snewn(clipboard_length + 1, wchar_t); memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t)); clipboard_contents[clipboard_length] = L'\0'; - return TRUE; + term_do_paste(term, clipboard_contents, clipboard_length); } } else { char *s = GlobalLock(clipdata); @@ -5327,15 +5590,17 @@ static int process_clipdata(HGLOBAL clipdata, int unicode) clipboard_contents, i); clipboard_length = i - 1; clipboard_contents[clipboard_length] = L'\0'; - return TRUE; + term_do_paste(term, clipboard_contents, clipboard_length); } } - return FALSE; + sfree(clipboard_contents); } -void request_paste(void *frontend) +static void wintw_clip_request_paste(TermWin *tw, int clipboard) { + assert(clipboard == CLIP_SYSTEM); + /* * I always thought pasting was synchronous in Windows; the * clipboard access functions certainly _look_ synchronous, @@ -5357,52 +5622,6 @@ void request_paste(void *frontend) hwnd, 0, &in_threadid); } -void get_clip(void *frontend, wchar_t **p, int *len) -{ - if (p) { - *p = clipboard_contents; - *len = clipboard_length; - } -} - -#if 0 -/* - * Move `lines' lines from position `from' to position `to' in the - * window. - */ -void optimised_move(void *frontend, int to, int from, int lines) -{ - RECT r; - int min, max; - - min = (to < from ? to : from); - max = to + from - min; - - r.left = offset_width; - r.right = offset_width + term->cols * font_width; - r.top = offset_height + min * font_height; - r.bottom = offset_height + (max + lines) * font_height; - ScrollWindow(hwnd, 0, (to - from) * font_height, &r, &r); -} -#endif - -/* - * Print a message box and perform a fatal exit. - */ -void fatalbox(const char *fmt, ...) -{ - va_list ap; - char *stuff, morestuff[100]; - - va_start(ap, fmt); - stuff = dupvprintf(fmt, ap); - va_end(ap); - sprintf(morestuff, "%.70s Fatal Error", appname); - MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK); - sfree(stuff); - cleanup_exit(1); -} - /* * Print a modal (Really Bad) message box and perform a fatal exit. */ @@ -5437,7 +5656,7 @@ void nonfatal(const char *fmt, ...) sfree(stuff); } -static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) +static bool flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) { if (p_FlashWindowEx) { FLASHWINFO fi; @@ -5449,12 +5668,12 @@ static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout) return (*p_FlashWindowEx)(&fi); } else - return FALSE; /* shrug */ + return false; /* shrug */ } static void flash_window(int mode); static long next_flash; -static int flashing = 0; +static bool flashing = false; /* * Timer for platforms where we must maintain window flashing manually @@ -5477,17 +5696,17 @@ static void flash_window(int mode) if ((mode == 0) || (beep_ind == B_IND_DISABLED)) { /* stop */ if (flashing) { - flashing = 0; + flashing = false; if (p_FlashWindowEx) flash_window_ex(FLASHW_STOP, 0, 0); else - FlashWindow(hwnd, FALSE); + FlashWindow(hwnd, false); } } else if (mode == 2) { /* start */ if (!flashing) { - flashing = 1; + flashing = true; if (p_FlashWindowEx) { /* For so-called "steady" mode, we use uCount=2, which * seems to be the traditional number of flashes used @@ -5500,7 +5719,7 @@ static void flash_window(int mode) 0 /* system cursor blink rate */); /* No need to schedule timer */ } else { - FlashWindow(hwnd, TRUE); + FlashWindow(hwnd, true); next_flash = schedule_timer(450, flash_window_timer, hwnd); } } @@ -5508,7 +5727,7 @@ static void flash_window(int mode) } else if ((mode == 1) && (beep_ind == B_IND_FLASH)) { /* maintain */ if (flashing && !p_FlashWindowEx) { - FlashWindow(hwnd, TRUE); /* toggle */ + FlashWindow(hwnd, true); /* toggle */ next_flash = schedule_timer(450, flash_window_timer, hwnd); } } @@ -5517,7 +5736,7 @@ static void flash_window(int mode) /* * Beep. */ -void do_beep(void *frontend, int mode) +static void wintw_bell(TermWin *tw, int mode) { if (mode == BELL_DEFAULT) { /* @@ -5540,8 +5759,8 @@ void do_beep(void *frontend, int mode) lastbeep = GetTickCount(); } else if (mode == BELL_WAVEFILE) { Filename *bell_wavefile = conf_get_filename(conf, CONF_bell_wavefile); - if (!PlaySound(bell_wavefile->path, NULL, - SND_ASYNC | SND_FILENAME)) { + if (!p_PlaySound || !p_PlaySound(bell_wavefile->path, NULL, + SND_ASYNC | SND_FILENAME)) { char buf[sizeof(bell_wavefile->path) + 80]; char otherbuf[100]; sprintf(buf, "Unable to play sound file\n%s\n" @@ -5563,7 +5782,7 @@ void do_beep(void *frontend, int mode) * We must beep in different ways depending on whether this * is a 95-series or NT-series OS. */ - if(osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) + if (osPlatformId == VER_PLATFORM_WIN32_NT) Beep(800, 100); else MessageBeep(-1); @@ -5579,13 +5798,13 @@ void do_beep(void *frontend, int mode) * Minimise or restore the window in response to a server-side * request. */ -void set_iconic(void *frontend, int iconic) +static void wintw_set_minimised(TermWin *tw, bool minimised) { if (IsIconic(hwnd)) { - if (!iconic) + if (!minimised) ShowWindow(hwnd, SW_RESTORE); } else { - if (iconic) + if (minimised) ShowWindow(hwnd, SW_MINIMIZE); } } @@ -5593,7 +5812,7 @@ void set_iconic(void *frontend, int iconic) /* * Move the window in response to a server-side request. */ -void move_window(void *frontend, int x, int y) +static void wintw_move(TermWin *tw, int x, int y) { int resize_action = conf_get_int(conf, CONF_resize_action); if (resize_action == RESIZE_DISABLED || @@ -5608,9 +5827,9 @@ void move_window(void *frontend, int x, int y) * Move the window to the top or bottom of the z-order in response * to a server-side request. */ -void set_zorder(void *frontend, int top) +static void wintw_set_zorder(TermWin *tw, bool top) { - if (conf_get_int(conf, CONF_alwaysontop)) + if (conf_get_bool(conf, CONF_alwaysontop)) return; /* ignore */ SetWindowPos(hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); @@ -5619,22 +5838,22 @@ void set_zorder(void *frontend, int top) /* * Refresh the window in response to a server-side request. */ -void refresh_window(void *frontend) +static void wintw_refresh(TermWin *tw) { - InvalidateRect(hwnd, NULL, TRUE); + InvalidateRect(hwnd, NULL, true); } /* * Maximise or restore the window in response to a server-side * request. */ -void set_zoomed(void *frontend, int zoomed) +static void wintw_set_maximised(TermWin *tw, bool maximised) { if (IsZoomed(hwnd)) { - if (!zoomed) + if (!maximised) ShowWindow(hwnd, SW_RESTORE); } else { - if (zoomed) + if (maximised) ShowWindow(hwnd, SW_MAXIMIZE); } } @@ -5642,7 +5861,7 @@ void set_zoomed(void *frontend, int zoomed) /* * Report whether the window is iconic, for terminal reports. */ -int is_iconic(void *frontend) +static bool wintw_is_minimised(TermWin *tw) { return IsIconic(hwnd); } @@ -5650,7 +5869,7 @@ int is_iconic(void *frontend) /* * Report the window's position, for terminal reports. */ -void get_window_pos(void *frontend, int *x, int *y) +static void wintw_get_pos(TermWin *tw, int *x, int *y) { RECT r; GetWindowRect(hwnd, &r); @@ -5661,7 +5880,7 @@ void get_window_pos(void *frontend, int *x, int *y) /* * Report the window's pixel size, for terminal reports. */ -void get_window_pixels(void *frontend, int *x, int *y) +static void wintw_get_pixels(TermWin *tw, int *x, int *y) { RECT r; GetWindowRect(hwnd, &r); @@ -5672,7 +5891,7 @@ void get_window_pixels(void *frontend, int *x, int *y) /* * Return the window or icon title. */ -char *get_window_title(void *frontend, int icon) +static const char *wintw_get_title(TermWin *tw, bool icon) { return icon ? icon_name : window_name; } @@ -5680,19 +5899,19 @@ char *get_window_title(void *frontend, int icon) /* * See if we're in full-screen mode. */ -static int is_full_screen() +static bool is_full_screen() { if (!IsZoomed(hwnd)) - return FALSE; + return false; if (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CAPTION) - return FALSE; - return TRUE; + return false; + return true; } /* Get the rect/size of a full screen window using the nearest available * monitor in multimon systems; default to something sensible if only * one monitor is present. */ -static int get_fullscreen_rect(RECT * ss) +static bool get_fullscreen_rect(RECT * ss) { #if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) HMONITOR mon; @@ -5703,7 +5922,7 @@ static int get_fullscreen_rect(RECT * ss) /* structure copy */ *ss = mi.rcMonitor; - return TRUE; + return true; #else /* could also use code like this: ss->left = ss->top = 0; @@ -5732,7 +5951,7 @@ static void make_full_screen() /* Remove the window furniture. */ style = GetWindowLongPtr(hwnd, GWL_STYLE); style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); - if (conf_get_int(conf, CONF_scrollbar_in_fullscreen)) + if (conf_get_bool(conf, CONF_scrollbar_in_fullscreen)) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; @@ -5771,7 +5990,7 @@ static void clear_full_screen() style &= ~WS_THICKFRAME; else style |= WS_THICKFRAME; - if (conf_get_int(conf, CONF_scrollbar)) + if (conf_get_bool(conf, CONF_scrollbar)) style |= WS_VSCROLL; else style &= ~WS_VSCROLL; @@ -5805,38 +6024,24 @@ static void flip_full_screen() } } -void frontend_keypress(void *handle) -{ - /* - * Keypress termination in non-Close-On-Exit mode is not - * currently supported in PuTTY proper, because the window - * always has a perfectly good Close button anyway. So we do - * nothing here. - */ - return; -} - -int from_backend(void *frontend, int is_stderr, const char *data, int len) +static int win_seat_output(Seat *seat, bool is_stderr, + const void *data, int len) { return term_data(term, is_stderr, data, len); } -int from_backend_untrusted(void *frontend, const char *data, int len) -{ - return term_data_untrusted(term, data, len); -} - -int from_backend_eof(void *frontend) +static bool win_seat_eof(Seat *seat) { - return TRUE; /* do respond to incoming EOF with outgoing */ + return true; /* do respond to incoming EOF with outgoing */ } -int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen) +static int win_seat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) { int ret; - ret = cmdline_get_passwd_input(p, in, inlen); + ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = term_get_userpass_input(term, p, in, inlen); + ret = term_get_userpass_input(term, p, input); return ret; } diff --git a/windows/wingss.c b/windows/wingss.c index 6aaa20cf..4b511b2a 100644 --- a/windows/wingss.c +++ b/windows/wingss.c @@ -1,5 +1,6 @@ #ifndef NO_GSSAPI +#include #include "putty.h" #define SECURITY_WIN32 @@ -11,11 +12,33 @@ #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 GSSAPI32.DLL", + "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".DLL", "Microsoft SSPI SECUR32.DLL", "User-specified GSSAPI DLL", }; @@ -27,7 +50,7 @@ const struct keyvalwhere gsslibkeywords[] = { DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, AcquireCredentialsHandleA, - (SEC_CHAR *, SEC_CHAR *, ULONG, PLUID, + (SEC_CHAR *, SEC_CHAR *, ULONG, PVOID, PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp)); DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, InitializeSecurityContextA, @@ -49,6 +72,12 @@ DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, 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; @@ -72,12 +101,21 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) 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 defined _MSC_VER && _MSC_VER < 1900 + /* 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 */ - /* TODO: For 64-bit builds, check for gssapi64.dll */ module = NULL; if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) == ERROR_SUCCESS) { @@ -93,8 +131,20 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, (LPBYTE)buffer, &size); if (ret == ERROR_SUCCESS && type == REG_SZ) { - strcat(buffer, "\\bin\\gssapi32.dll"); - module = LoadLibrary(buffer); + 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); } @@ -105,7 +155,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) &list->libraries[list->nlibraries++]; lib->id = 0; - lib->gsslogmsg = "Using GSSAPI from GSSAPI32.DLL"; + lib->gsslogmsg = "Using GSSAPI from GSSAPI"MIT_KERB_SUFFIX".DLL"; lib->handle = (void *)module; #define BIND_GSS_FN(name) \ @@ -114,6 +164,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) 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); @@ -142,6 +193,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) 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); } @@ -152,7 +204,32 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) module = NULL; path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; if (*path) { - module = LoadLibrary(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 = @@ -169,6 +246,7 @@ struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) 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); @@ -234,7 +312,8 @@ static Ssh_gss_stat ssh_sspi_import_name(struct ssh_gss_library *lib, } static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, - Ssh_gss_ctx *ctx) + Ssh_gss_ctx *ctx, + time_t *expiry) { winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx); memset(winctx, 0, sizeof(winSsh_gss_ctx)); @@ -254,21 +333,83 @@ static Ssh_gss_stat ssh_sspi_acquire_cred(struct ssh_gss_library *lib, NULL, NULL, &winctx->cred_handle, - &winctx->expiry); + 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; - if (winctx->maj_stat != SEC_E_OK) return SSH_GSS_FAILURE; - *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) + 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}; @@ -278,6 +419,7 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, 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; @@ -292,8 +434,10 @@ static Ssh_gss_stat ssh_sspi_init_sec_context(struct ssh_gss_library *lib, &winctx->context, &output_desc, &ret_flags, - &winctx->expiry); - + &localexp); + + localexp_to_exp_lifetime(&localexp, expiry, lifetime); + /* prepare for the next round */ winctx->context_handle = &winctx->context; send_tok->value = wsend_tok.pvBuffer; @@ -448,6 +592,36 @@ static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib, 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) { @@ -465,6 +639,7 @@ static void ssh_sspi_bind_fns(struct ssh_gss_library *lib) 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; } diff --git a/windows/winhandl.c b/windows/winhandl.c index cfd62987..2a59885b 100644 --- a/windows/winhandl.c +++ b/windows/winhandl.c @@ -58,10 +58,10 @@ struct handle_generic { HANDLE h; /* the handle itself */ HANDLE ev_to_main; /* event used to signal main thread */ HANDLE ev_from_main; /* event used to signal back to us */ - int moribund; /* are we going to kill this soon? */ - int done; /* request subthread to terminate */ - int defunct; /* has the subthread already gone? */ - int busy; /* operation currently in progress? */ + 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 */ }; @@ -81,10 +81,10 @@ struct handle_input { HANDLE h; /* the handle itself */ HANDLE ev_to_main; /* event used to signal main thread */ HANDLE ev_from_main; /* event used to signal back to us */ - int moribund; /* are we going to kill this soon? */ - int done; /* request subthread to terminate */ - int defunct; /* has the subthread already gone? */ - int busy; /* operation currently in progress? */ + 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 */ /* @@ -115,11 +115,12 @@ static DWORD WINAPI handle_input_threadfunc(void *param) struct handle_input *ctx = (struct handle_input *) param; OVERLAPPED ovl, *povl; HANDLE oev; - int readret, readlen, finished; + bool readret, finished; + int readlen; if (ctx->flags & HANDLE_FLAG_OVERLAPPED) { povl = &ovl; - oev = CreateEvent(NULL, TRUE, FALSE, NULL); + oev = CreateEvent(NULL, true, false, NULL); } else { povl = NULL; } @@ -141,7 +142,7 @@ static DWORD WINAPI handle_input_threadfunc(void *param) ctx->readerr = 0; if (povl && !readret && ctx->readerr == ERROR_IO_PENDING) { WaitForSingleObject(povl->hEvent, INFINITE); - readret = GetOverlappedResult(ctx->h, povl, &ctx->len, FALSE); + readret = GetOverlappedResult(ctx->h, povl, &ctx->len, false); if (!readret) ctx->readerr = GetLastError(); else @@ -223,7 +224,7 @@ static void handle_throttle(struct handle_input *ctx, int backlog) */ if (backlog < MAX_BACKLOG) { SetEvent(ctx->ev_from_main); - ctx->busy = TRUE; + ctx->busy = true; } } @@ -241,10 +242,10 @@ struct handle_output { HANDLE h; /* the handle itself */ HANDLE ev_to_main; /* event used to signal main thread */ HANDLE ev_from_main; /* event used to signal back to us */ - int moribund; /* are we going to kill this soon? */ - int done; /* request subthread to terminate */ - int defunct; /* has the subthread already gone? */ - int busy; /* operation currently in progress? */ + 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 */ /* @@ -284,11 +285,11 @@ static DWORD WINAPI handle_output_threadfunc(void *param) struct handle_output *ctx = (struct handle_output *) param; OVERLAPPED ovl, *povl; HANDLE oev; - int writeret; + bool writeret; if (ctx->flags & HANDLE_FLAG_OVERLAPPED) { povl = &ovl; - oev = CreateEvent(NULL, TRUE, FALSE, NULL); + oev = CreateEvent(NULL, true, false, NULL); } else { povl = NULL; } @@ -318,7 +319,7 @@ static DWORD WINAPI handle_output_threadfunc(void *param) ctx->writeerr = 0; if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) { writeret = GetOverlappedResult(ctx->h, povl, - &ctx->lenwritten, TRUE); + &ctx->lenwritten, true); if (!writeret) ctx->writeerr = GetLastError(); else @@ -354,7 +355,7 @@ static void handle_try_output(struct handle_output *ctx) ctx->buffer = senddata; ctx->len = sendlen; SetEvent(ctx->ev_from_main); - ctx->busy = TRUE; + ctx->busy = true; } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && ctx->outgoingeof == EOF_PENDING) { CloseHandle(ctx->h); @@ -377,10 +378,10 @@ struct handle_foreign { HANDLE h; /* the handle itself */ HANDLE ev_to_main; /* event used to signal main thread */ HANDLE ev_from_main; /* event used to signal back to us */ - int moribund; /* are we going to kill this soon? */ - int done; /* request subthread to terminate */ - int defunct; /* has the subthread already gone? */ - int busy; /* operation currently in progress? */ + 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 */ /* @@ -440,12 +441,12 @@ 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.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.defunct = false; + h->u.i.moribund = false; + h->u.i.done = false; h->u.i.privdata = privdata; h->u.i.flags = flags; @@ -455,7 +456,7 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, CreateThread(NULL, 0, handle_input_threadfunc, &h->u.i, 0, &in_threadid); - h->u.i.busy = TRUE; + h->u.i.busy = true; return h; } @@ -468,12 +469,12 @@ 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; - h->u.o.moribund = FALSE; - h->u.o.done = FALSE; + 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; @@ -499,13 +500,13 @@ struct handle *handle_add_foreign_event(HANDLE event, 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.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; + h->u.f.busy = true; if (!handles_by_evtomain) handles_by_evtomain = newtree234(handle_cmp_evtomain); @@ -592,7 +593,7 @@ void handle_free(struct handle *h) * we set the moribund flag, which will be noticed next time * an operation completes. */ - h->u.g.moribund = TRUE; + h->u.g.moribund = true; } else if (h->u.g.defunct) { /* * There isn't even a subthread; we can go straight to @@ -605,9 +606,9 @@ void handle_free(struct handle *h) * 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; + h->u.g.moribund = true; + h->u.g.done = true; + h->u.g.busy = true; SetEvent(h->u.g.ev_from_main); } } @@ -642,8 +643,8 @@ void handle_got_event(HANDLE event) if (h->u.g.done) { handle_destroy(h); } else { - h->u.g.done = TRUE; - h->u.g.busy = TRUE; + h->u.g.done = true; + h->u.g.busy = true; SetEvent(h->u.g.ev_from_main); } return; @@ -653,7 +654,7 @@ void handle_got_event(HANDLE event) int backlog; case HT_INPUT: - h->u.i.busy = FALSE; + h->u.i.busy = false; /* * A signal on an input handle means data has arrived. @@ -662,7 +663,7 @@ void handle_got_event(HANDLE event) /* * EOF, or (nearly equivalently) read error. */ - h->u.i.defunct = TRUE; + h->u.i.defunct = true; h->u.i.gotdata(h, NULL, -h->u.i.readerr); } else { backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); @@ -671,7 +672,7 @@ void handle_got_event(HANDLE event) break; case HT_OUTPUT: - h->u.o.busy = FALSE; + h->u.o.busy = false; /* * A signal on an output handle means we have completed a @@ -684,7 +685,7 @@ void handle_got_event(HANDLE event) * and mark the thread as defunct (because the output * thread is terminating by now). */ - h->u.o.defunct = TRUE; + h->u.o.defunct = true; h->u.o.sentdata(h, -h->u.o.writeerr); } else { bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); diff --git a/windows/winhelp.c b/windows/winhelp.c index 47539e53..53e8dc1e 100644 --- a/windows/winhelp.c +++ b/windows/winhelp.c @@ -15,11 +15,11 @@ #include #endif /* NO_HTMLHELP */ -static int requested_help; +static bool requested_help; static char *help_path; -static int help_has_contents; +static bool help_has_contents; #ifndef NO_HTMLHELP -DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD)); +DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR)); static char *chm_path; #endif /* NO_HTMLHELP */ @@ -42,10 +42,10 @@ void init_help(void) help_path = NULL; strcpy(r, PUTTY_HELP_CONTENTS); if ( (fp = fopen(b, "r")) != NULL) { - help_has_contents = TRUE; + help_has_contents = true; fclose(fp); } else - help_has_contents = FALSE; + help_has_contents = false; #ifndef NO_HTMLHELP strcpy(r, PUTTY_CHM_FILE); @@ -74,7 +74,7 @@ void shutdown_help(void) * call HH_UNINITIALIZE.) */ } -int has_help(void) +bool has_help(void) { /* * FIXME: it would be nice here to disregard help_path on @@ -120,7 +120,7 @@ void launch_help(HWND hwnd, const char *topic) help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0); } } - requested_help = TRUE; + requested_help = true; } void quit_help(HWND hwnd) @@ -134,6 +134,6 @@ void quit_help(HWND hwnd) if (help_path) { WinHelp(hwnd, help_path, HELP_QUIT, 0); } - requested_help = FALSE; + requested_help = false; } } diff --git a/windows/winhelp.h b/windows/winhelp.h index 761c8c76..2ecb537a 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -23,6 +23,7 @@ #define WINHELP_CTX_logging_filename "logging.filename:config-logfilename" #define WINHELP_CTX_logging_exists "logging.exists:config-logfileexists" #define WINHELP_CTX_logging_flush "logging.flush:config-logflush" +#define WINHELP_CTX_logging_header "logging.header:config-logheader" #define WINHELP_CTX_logging_ssh_omit_password "logging.ssh.omitpassword:config-logssh" #define WINHELP_CTX_logging_ssh_omit_data "logging.ssh.omitdata:config-logssh" #define WINHELP_CTX_keyboard_backspace "keyboard.backspace:config-backspace" @@ -104,6 +105,7 @@ #define WINHELP_CTX_ssh_share "ssh.sharing:config-ssh-sharing" #define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order" #define WINHELP_CTX_ssh_hklist "ssh.hostkey.order:config-ssh-hostkey-order" +#define WINHELP_CTX_ssh_gssapi_kex_delegation "ssh.kex.gssapi.delegation:config-ssh-kex-gssapi-delegation" #define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "ssh.kex.manualhostkeys:config-ssh-kex-manual-hostkeys" #define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth" @@ -120,11 +122,15 @@ #define WINHELP_CTX_selection_buttons "selection.buttons:config-mouse" #define WINHELP_CTX_selection_shiftdrag "selection.shiftdrag:config-mouseshift" #define WINHELP_CTX_selection_rect "selection.rect:config-rectselect" -#define WINHELP_CTX_selection_charclasses "selection.charclasses:config-charclasses" #define WINHELP_CTX_selection_linedraw "selection.linedraw:config-linedrawpaste" -#define WINHELP_CTX_selection_rtf "selection.rtf:config-rtfpaste" +#define WINHELP_CTX_selection_autocopy "selection.autocopy:config-selection-autocopy" +#define WINHELP_CTX_selection_clipactions "selection.clipactions:config-selection-clipactions" +#define WINHELP_CTX_selection_pastectrl "selection.pastectrl:config-paste-ctrl-char" +#define WINHELP_CTX_copy_charclasses "copy.charclasses:config-charclasses" +#define WINHELP_CTX_copy_rtf "copy.rtf:config-rtfcopy" #define WINHELP_CTX_colours_ansi "colours.ansi:config-ansicolour" #define WINHELP_CTX_colours_xterm256 "colours.xterm256:config-xtermcolour" +#define WINHELP_CTX_colours_truecolour "colours.truecolour:config-truecolour" #define WINHELP_CTX_colours_bold "colours.bold:config-boldcolour" #define WINHELP_CTX_colours_system "colours.system:config-syscolour" #define WINHELP_CTX_colours_logpal "colours.logpal:config-logpalette" @@ -133,6 +139,7 @@ #define WINHELP_CTX_translation_cjk_ambig_wide "translation.cjkambigwide:config-cjk-ambig-wide" #define WINHELP_CTX_translation_cyrillic "translation.cyrillic:config-cyr" #define WINHELP_CTX_translation_linedraw "translation.linedraw:config-linedraw" +#define WINHELP_CTX_translation_utf8linedraw "translation.utf8linedraw:config-utf8linedraw" #define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11:config-ssh-x11" #define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth:config-ssh-x11auth" #define WINHELP_CTX_ssh_tunnels_xauthority "ssh.tunnels.xauthority:config-ssh-xauthority" diff --git a/windows/winhsock.c b/windows/winhsock.c index 1a4ee4d7..71f2c188 100644 --- a/windows/winhsock.c +++ b/windows/winhsock.c @@ -7,17 +7,11 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "tree234.h" #include "putty.h" #include "network.h" -typedef struct Socket_handle_tag *Handle_Socket; - -struct Socket_handle_tag { - const struct socket_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - +typedef struct HandleSocket { HANDLE send_H, recv_H, stderr_H; struct handle *send_h, *recv_h, *stderr_h; @@ -42,33 +36,36 @@ struct Socket_handle_tag { /* Data received from stderr_H, if we have one. */ bufchain stderrdata; - int defer_close, deferred_close; /* in case of re-entrance */ + bool defer_close, deferred_close; /* in case of re-entrance */ char *error; - Plug plug; -}; + Plug *plug; + + Socket sock; +} HandleSocket; static int handle_gotdata(struct handle *h, void *data, int len) { - Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (len < 0) { - return plug_closing(ps->plug, "Read error from handle", - 0, 0); + plug_closing(hs->plug, "Read error from handle", 0, 0); + return 0; } else if (len == 0) { - return plug_closing(ps->plug, NULL, 0, 0); + plug_closing(hs->plug, NULL, 0, 0); + return 0; } else { - assert(ps->frozen != FROZEN && ps->frozen != THAWING); - if (ps->frozen == FREEZING) { + 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(&ps->inputdata, data, len); - ps->frozen = FROZEN; + bufchain_add(&hs->inputdata, data, len); + hs->frozen = FROZEN; /* * And return a very large backlog, to prevent further @@ -76,73 +73,74 @@ static int handle_gotdata(struct handle *h, void *data, int len) */ return INT_MAX; } else { - return plug_receive(ps->plug, 0, data, len); + plug_receive(hs->plug, 0, data, len); + return 0; } } } static int handle_stderr(struct handle *h, void *data, int len) { - Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (len > 0) - log_proxy_stderr(ps->plug, &ps->stderrdata, data, len); + log_proxy_stderr(hs->plug, &hs->stderrdata, data, len); return 0; } static void handle_sentdata(struct handle *h, int new_backlog) { - Handle_Socket ps = (Handle_Socket) handle_get_privdata(h); + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); if (new_backlog < 0) { /* Special case: this is actually reporting an error writing * to the underlying handle, and our input value is the error * code itself, negated. */ - plug_closing(ps->plug, win_strerror(-new_backlog), -new_backlog, 0); + plug_closing(hs->plug, win_strerror(-new_backlog), -new_backlog, 0); return; } - plug_sent(ps->plug, new_backlog); + plug_sent(hs->plug, new_backlog); } -static Plug sk_handle_plug(Socket s, Plug p) +static Plug *sk_handle_plug(Socket *s, Plug *p) { - Handle_Socket ps = (Handle_Socket) s; - Plug ret = ps->plug; + HandleSocket *hs = container_of(s, HandleSocket, sock); + Plug *ret = hs->plug; if (p) - ps->plug = p; + hs->plug = p; return ret; } -static void sk_handle_close(Socket s) +static void sk_handle_close(Socket *s) { - Handle_Socket ps = (Handle_Socket) s; + HandleSocket *hs = container_of(s, HandleSocket, sock); - if (ps->defer_close) { - ps->deferred_close = TRUE; + if (hs->defer_close) { + hs->deferred_close = true; return; } - handle_free(ps->send_h); - handle_free(ps->recv_h); - CloseHandle(ps->send_H); - if (ps->recv_H != ps->send_H) - CloseHandle(ps->recv_H); - bufchain_clear(&ps->inputdata); - bufchain_clear(&ps->stderrdata); + 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); + bufchain_clear(&hs->stderrdata); - sfree(ps); + sfree(hs); } -static int sk_handle_write(Socket s, const char *data, int len) +static int sk_handle_write(Socket *s, const void *data, int len) { - Handle_Socket ps = (Handle_Socket) s; + HandleSocket *hs = container_of(s, HandleSocket, sock); - return handle_write(ps->send_h, data, len); + return handle_write(hs->send_h, data, len); } -static int sk_handle_write_oob(Socket s, const char *data, int len) +static int sk_handle_write_oob(Socket *s, const void *data, int len) { /* * oob data is treated as inband; nasty, but nothing really @@ -151,72 +149,72 @@ static int sk_handle_write_oob(Socket s, const char *data, int len) return sk_handle_write(s, data, len); } -static void sk_handle_write_eof(Socket s) +static void sk_handle_write_eof(Socket *s) { - Handle_Socket ps = (Handle_Socket) s; + HandleSocket *hs = container_of(s, HandleSocket, sock); - handle_write_eof(ps->send_h); + handle_write_eof(hs->send_h); } -static void sk_handle_flush(Socket s) +static void sk_handle_flush(Socket *s) { - /* Handle_Socket ps = (Handle_Socket) s; */ + /* HandleSocket *hs = container_of(s, HandleSocket, sock); */ /* do nothing */ } -static void handle_socket_unfreeze(void *psv) +static void handle_socket_unfreeze(void *hsv) { - Handle_Socket ps = (Handle_Socket) psv; + HandleSocket *hs = (HandleSocket *)hsv; void *data; - int len, new_backlog; + int len; /* * If we've been put into a state other than THAWING since the * last callback, then we're done. */ - if (ps->frozen != THAWING) + if (hs->frozen != THAWING) return; /* * Get some of the data we've buffered. */ - bufchain_prefix(&ps->inputdata, &data, &len); + bufchain_prefix(&hs->inputdata, &data, &len); assert(len > 0); /* * Hand it off to the plug. Be careful of re-entrance - that might * have the effect of trying to close this socket. */ - ps->defer_close = TRUE; - new_backlog = plug_receive(ps->plug, 0, data, len); - bufchain_consume(&ps->inputdata, len); - ps->defer_close = FALSE; - if (ps->deferred_close) { - sk_handle_close(ps); + hs->defer_close = true; + plug_receive(hs->plug, 0, data, len); + bufchain_consume(&hs->inputdata, len); + hs->defer_close = false; + if (hs->deferred_close) { + sk_handle_close(&hs->sock); return; } - if (bufchain_size(&ps->inputdata) > 0) { + 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, ps); + queue_toplevel_callback(handle_socket_unfreeze, hs); } else { /* * Otherwise, we've successfully thawed! */ - ps->frozen = UNFROZEN; - handle_unthrottle(ps->recv_h, new_backlog); + hs->frozen = UNFROZEN; + handle_unthrottle(hs->recv_h, 0); } } -static void sk_handle_set_frozen(Socket s, int is_frozen) +static void sk_handle_set_frozen(Socket *s, bool is_frozen) { - Handle_Socket ps = (Handle_Socket) s; + HandleSocket *hs = container_of(s, HandleSocket, sock); if (is_frozen) { - switch (ps->frozen) { + switch (hs->frozen) { case FREEZING: case FROZEN: return; /* nothing to do */ @@ -228,7 +226,7 @@ static void sk_handle_set_frozen(Socket s, int is_frozen) * throttled, so just return to FROZEN state. The toplevel * callback will notice and disable itself. */ - ps->frozen = FROZEN; + hs->frozen = FROZEN; break; case UNFROZEN: @@ -236,11 +234,11 @@ static void sk_handle_set_frozen(Socket s, int is_frozen) * The normal case. Go to FREEZING, and expect one more * load of data from winhandl if we're unlucky. */ - ps->frozen = FREEZING; + hs->frozen = FREEZING; break; } } else { - switch (ps->frozen) { + switch (hs->frozen) { case UNFROZEN: case THAWING: return; /* nothing to do */ @@ -251,8 +249,8 @@ static void sk_handle_set_frozen(Socket s, int is_frozen) * we were frozen, then we'll still be in this state and * can just unfreeze in the trivial way. */ - assert(bufchain_size(&ps->inputdata) == 0); - ps->frozen = UNFROZEN; + assert(bufchain_size(&hs->inputdata) == 0); + hs->frozen = UNFROZEN; break; case FROZEN: @@ -260,21 +258,21 @@ static void sk_handle_set_frozen(Socket s, int is_frozen) * If we have buffered data, go to THAWING and start * releasing it in top-level callbacks. */ - ps->frozen = THAWING; - queue_toplevel_callback(handle_socket_unfreeze, ps); + hs->frozen = THAWING; + queue_toplevel_callback(handle_socket_unfreeze, hs); } } } -static const char *sk_handle_socket_error(Socket s) +static const char *sk_handle_socket_error(Socket *s) { - Handle_Socket ps = (Handle_Socket) s; - return ps->error; + HandleSocket *hs = container_of(s, HandleSocket, sock); + return hs->error; } -static char *sk_handle_peer_info(Socket s) +static SocketPeerInfo *sk_handle_peer_info(Socket *s) { - Handle_Socket ps = (Handle_Socket) s; + HandleSocket *hs = container_of(s, HandleSocket, sock); ULONG pid; static HMODULE kernel32_module; DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId, @@ -282,7 +280,17 @@ static char *sk_handle_peer_info(Socket s) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); - GET_WINDOWS_FUNCTION(kernel32_module, GetNamedPipeClientProcessId); +#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__ || defined COVERITY + /* 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 } /* @@ -291,48 +299,54 @@ static char *sk_handle_peer_info(Socket s) * to log what we can find out about the client end. */ if (p_GetNamedPipeClientProcessId && - p_GetNamedPipeClientProcessId(ps->send_H, &pid)) - return dupprintf("process id %lu", (unsigned long)pid); + 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; } -Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug plug, int overlapped) +static const SocketVtable HandleSocket_sockvt = { + sk_handle_plug, + sk_handle_close, + sk_handle_write, + sk_handle_write_oob, + sk_handle_write_eof, + sk_handle_flush, + sk_handle_set_frozen, + sk_handle_socket_error, + sk_handle_peer_info, +}; + +Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, + Plug *plug, bool overlapped) { - static const struct socket_function_table socket_fn_table = { - sk_handle_plug, - sk_handle_close, - sk_handle_write, - sk_handle_write_oob, - sk_handle_write_eof, - sk_handle_flush, - sk_handle_set_frozen, - sk_handle_socket_error, - sk_handle_peer_info, - }; - - Handle_Socket ret; + HandleSocket *hs; int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); - ret = snew(struct Socket_handle_tag); - ret->fn = &socket_fn_table; - ret->plug = plug; - ret->error = NULL; - ret->frozen = UNFROZEN; - bufchain_init(&ret->inputdata); - bufchain_init(&ret->stderrdata); - - ret->recv_H = recv_H; - ret->recv_h = handle_input_new(ret->recv_H, handle_gotdata, ret, flags); - ret->send_H = send_H; - ret->send_h = handle_output_new(ret->send_H, handle_sentdata, ret, flags); - ret->stderr_H = stderr_H; - if (ret->stderr_H) - ret->stderr_h = handle_input_new(ret->stderr_H, handle_stderr, - ret, flags); - - ret->defer_close = ret->deferred_close = FALSE; - - return (Socket) ret; + hs = snew(HandleSocket); + hs->sock.vt = &HandleSocket_sockvt; + hs->plug = plug; + hs->error = NULL; + hs->frozen = UNFROZEN; + bufchain_init(&hs->inputdata); + bufchain_init(&hs->stderrdata); + + 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 index 85963a32..d33b5ce0 100644 --- a/windows/winjump.c +++ b/windows/winjump.c @@ -383,7 +383,6 @@ static IShellLink *make_shell_link(const char *appname, { IShellLink *ret; char *app_path, *param_string, *desc_string; - void *psettings_tmp; IPropertyStore *pPS; PROPVARIANT pv; @@ -409,7 +408,7 @@ static IShellLink *make_shell_link(const char *appname, /* Check if this is a valid session, otherwise don't add. */ if (sessionname) { - psettings_tmp = open_settings_r(sessionname); + settings_r *psettings_tmp = open_settings_r(sessionname); if (!psettings_tmp) { sfree(app_path); return NULL; @@ -493,7 +492,7 @@ static void update_jumplist_from_registry(void) IObjectArray *array = NULL; IShellLink *link = NULL; IObjectArray *pRemoved = NULL; - int need_abort = FALSE; + bool need_abort = false; /* * Create an ICustomDestinationList: the top-level object which @@ -514,7 +513,7 @@ static void update_jumplist_from_registry(void) if (!SUCCEEDED(pCDL->lpVtbl->BeginList(pCDL, &num_items, COMPTR(IObjectArray, &pRemoved)))) goto cleanup; - need_abort = TRUE; + need_abort = true; if (!SUCCEEDED(pRemoved->lpVtbl->GetCount(pRemoved, &nremoved))) nremoved = 0; @@ -539,12 +538,12 @@ static void update_jumplist_from_registry(void) link = make_shell_link(NULL, piterator); if (link) { UINT i; - int found; + bool found; /* * Check that the link isn't in the user-removed list. */ - for (i = 0, found = FALSE; i < nremoved && !found; i++) { + for (i = 0, found = false; i < nremoved && !found; i++) { IShellLink *rlink; if (SUCCEEDED(pRemoved->lpVtbl->GetAt (pRemoved, i, COMPTR(IShellLink, &rlink)))) { @@ -554,7 +553,7 @@ static void update_jumplist_from_registry(void) SUCCEEDED(rlink->lpVtbl->GetDescription (rlink, desc2, sizeof(desc2)-1)) && !strcmp(desc1, desc2)) { - found = TRUE; + found = true; } rlink->lpVtbl->Release(rlink); } @@ -657,7 +656,7 @@ static void update_jumplist_from_registry(void) * Commit the jump list. */ pCDL->lpVtbl->CommitList(pCDL); - need_abort = FALSE; + need_abort = false; /* * Clean up. @@ -688,8 +687,7 @@ void clear_jumplist(void) /* Adds a saved session to the Windows 7 jumplist. */ void add_session_to_jumplist(const char * const sessionname) { - if ((osVersion.dwMajorVersion < 6) || - (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1)) + if ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 1)) return; /* do nothing on pre-Win7 systems */ if (add_to_jumplist_registry(sessionname) == JUMPLISTREG_OK) { @@ -703,8 +701,7 @@ void add_session_to_jumplist(const char * const sessionname) /* Removes a saved session from the Windows jumplist. */ void remove_session_from_jumplist(const char * const sessionname) { - if ((osVersion.dwMajorVersion < 6) || - (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion < 1)) + if ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 1)) return; /* do nothing on pre-Win7 systems */ if (remove_from_jumplist_registry(sessionname) == JUMPLISTREG_OK) { @@ -718,7 +715,7 @@ void remove_session_from_jumplist(const char * const sessionname) /* Set Explicit App User Model Id to fix removable media error with jump lists */ -BOOL set_explicit_app_user_model_id() +bool set_explicit_app_user_model_id(void) { DECL_WINDOWS_FUNCTION(static, HRESULT, SetCurrentProcessExplicitAppUserModelID, (PCWSTR)); @@ -728,19 +725,25 @@ BOOL set_explicit_app_user_model_id() if (!shell32_module) { shell32_module = load_system32_dll("Shell32.dll"); - GET_WINDOWS_FUNCTION(shell32_module, SetCurrentProcessExplicitAppUserModelID); + /* + * 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 true; } - return FALSE; + return false; } /* Function doesn't exist, which is ok for Pre-7 systems */ - return TRUE; + return true; } diff --git a/windows/winmisc.c b/windows/winmisc.c index 11e2ca0f..86f413e8 100644 --- a/windows/winmisc.c +++ b/windows/winmisc.c @@ -4,13 +4,14 @@ #include #include +#include #include "putty.h" #ifndef SECURITY_WIN32 #define SECURITY_WIN32 #endif #include -OSVERSIONINFO osVersion; +DWORD osMajorVersion, osMinorVersion, osPlatformId; char *platform_get_x_display(void) { /* We may as well check for DISPLAY in case it's useful. */ @@ -34,12 +35,12 @@ const char *filename_to_str(const Filename *fn) return fn->path; } -int filename_equal(const Filename *f1, const Filename *f2) +bool filename_equal(const Filename *f1, const Filename *f2) { return !strcmp(f1->path, f2->path); } -int filename_is_null(const Filename *fn) +bool filename_is_null(const Filename *fn) { return !*fn->path; } @@ -50,25 +51,13 @@ void filename_free(Filename *fn) sfree(fn); } -int filename_serialise(const Filename *f, void *vdata) +void filename_serialise(BinarySink *bs, const Filename *f) { - char *data = (char *)vdata; - int len = strlen(f->path) + 1; /* include trailing NUL */ - if (data) { - strcpy(data, f->path); - } - return len; + put_asciz(bs, f->path); } -Filename *filename_deserialise(void *vdata, int maxsize, int *used) +Filename *filename_deserialise(BinarySource *src) { - char *data = (char *)vdata; - char *end; - end = memchr(data, '\0', maxsize); - if (!end) - return NULL; - end++; - *used = end - data; - return filename_from_str(data); + return filename_from_str(get_asciz(src)); } char filename_char_sanitise(char c) @@ -92,17 +81,23 @@ char *get_username(void) { DWORD namelen; char *user; - int got_username = FALSE; + bool got_username = false; DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); { - static int tried_usernameex = FALSE; + 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; + tried_usernameex = true; } } @@ -130,7 +125,7 @@ char *get_username(void) if (!got_username) { /* Fall back to local user name */ namelen = 0; - if (GetUserName(NULL, &namelen) == FALSE) { + 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 @@ -172,20 +167,61 @@ void dll_hijacking_protection(void) if (!kernel32_module) { kernel32_module = load_system32_dll("kernel32.dll"); +#if (defined _MSC_VER && _MSC_VER < 1900) || defined COVERITY + /* For older Visual Studio, and also for the system I + * currently use for Coveritying the Windows code, 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 only */ - p_SetDefaultDllDirectories(0x800); + /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified + * directories only */ + p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS); } } -BOOL init_winver(void) +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); - return GetVersionEx ( (OSVERSIONINFO *) &osVersion); + 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) @@ -523,8 +559,7 @@ void *minefield_c_realloc(void *p, size_t size) #endif /* MINEFIELD */ -FontSpec *fontspec_new(const char *name, - int bold, int height, int charset) +FontSpec *fontspec_new(const char *name, bool bold, int height, int charset) { FontSpec *f = snew(FontSpec); f->name = dupstr(name); @@ -542,31 +577,89 @@ void fontspec_free(FontSpec *f) sfree(f->name); sfree(f); } -int fontspec_serialise(FontSpec *f, void *vdata) +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) { - char *data = (char *)vdata; - int len = strlen(f->name) + 1; /* include trailing NUL */ - if (data) { - strcpy(data, f->name); - PUT_32BIT_MSB_FIRST(data + len, f->isbold); - PUT_32BIT_MSB_FIRST(data + len + 4, f->height); - PUT_32BIT_MSB_FIRST(data + len + 8, f->charset); + 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++); + } } - return len + 12; /* also include three 4-byte ints */ -} -FontSpec *fontspec_deserialise(void *vdata, int maxsize, int *used) -{ - char *data = (char *)vdata; - char *end; - if (maxsize < 13) - return NULL; - end = memchr(data, '\0', maxsize-12); - if (!end) - return NULL; - end++; - *used = end - data + 12; - return fontspec_new(data, - GET_32BIT_MSB_FIRST(end), - GET_32BIT_MSB_FIRST(end + 4), - GET_32BIT_MSB_FIRST(end + 8)); } diff --git a/windows/winnet.c b/windows/winnet.c index 98753237..bf91634f 100644 --- a/windows/winnet.c +++ b/windows/winnet.c @@ -11,7 +11,6 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #define NEED_DECLARATION_OF_SELECT /* in order to initialise it */ #include "putty.h" @@ -35,16 +34,6 @@ const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; #define ipv4_is_loopback(addr) \ ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) -/* - * We used to typedef struct Socket_tag *Socket. - * - * Since we have made the networking abstraction slightly more - * abstract, Socket no longer means a tcp socket (it could mean - * an ssl socket). So now we must use Actual_Socket when we know - * we are talking about a tcp socket. - */ -typedef struct Socket_tag *Actual_Socket; - /* * Mutable state that goes with a SockAddr: stores information * about where in the list of candidate IP(v*) addresses we've @@ -58,42 +47,43 @@ struct SockAddrStep_tag { int curraddr; }; -struct Socket_tag { - const struct socket_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ +typedef struct NetSocket NetSocket; +struct NetSocket { const char *error; SOCKET s; - Plug plug; + Plug *plug; bufchain output_data; - int connected; - int writable; - int frozen; /* this causes readability notifications to be ignored */ - int frozen_readable; /* this means we missed at least one readability - * notification while we were frozen */ - int localhost_only; /* for listening sockets */ + 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]; int sending_oob; - int oobinline, nodelay, keepalive, privport; + bool oobinline, nodelay, keepalive, privport; enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - SockAddr addr; + SockAddr *addr; SockAddrStep step; int port; - int pending_error; /* in case send() returns error */ + int pending_error; /* in case send() returns error */ /* * We sometimes need pairs of Socket structures to be linked: * if we are listening on the same IPv6 and v4 port, for * example. So here we define `parent' and `child' pointers to * track this link. */ - Actual_Socket parent, child; + NetSocket *parent, *child; + + Socket sock; }; -struct SockAddr_tag { +struct SockAddr { int refcount; char *error; - int resolved; - int namedpipe; /* indicates that this SockAddr is phony, holding a Windows - * named pipe pathname instead of a network address */ + 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 @@ -133,7 +123,7 @@ static tree234 *sktree; static int cmpfortree(void *av, void *bv) { - Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv; + NetSocket *a = (NetSocket *)av, *b = (NetSocket *)bv; unsigned long as = (unsigned long) a->s, bs = (unsigned long) b->s; if (as < bs) return -1; @@ -148,7 +138,7 @@ static int cmpfortree(void *av, void *bv) static int cmpforsearch(void *av, void *bv) { - Actual_Socket b = (Actual_Socket) bv; + NetSocket *b = (NetSocket *)bv; uintptr_t as = (uintptr_t) av, bs = (uintptr_t) b->s; if (as < bs) return -1; @@ -201,8 +191,8 @@ DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res)); DECL_WINDOWS_FUNCTION(static, int, getnameinfo, (const struct sockaddr FAR * sa, socklen_t salen, - char FAR * host, size_t hostlen, char FAR * serv, - size_t servlen, int flags)); + 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, @@ -216,28 +206,21 @@ static HMODULE winsock2_module = NULL; static HMODULE wship6_module = NULL; #endif -int sk_startup(int hi, int lo) +static bool sk_startup(int hi, int lo) { WORD winsock_ver; winsock_ver = MAKEWORD(hi, lo); if (p_WSAStartup(winsock_ver, &wsadata)) { - return FALSE; + return false; } if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) { - return FALSE; + return false; } -#ifdef NET_SETUP_DIAGNOSTICS - { - char buf[80]; - sprintf(buf, "Using WinSock %d.%d", hi, lo); - logevent(NULL, buf); - } -#endif - return TRUE; + return true; } /* Actually define this function pointer, which won't have been @@ -257,60 +240,71 @@ void sk_init(void) winsock_module = load_system32_dll("wsock32.dll"); } if (!winsock_module) - fatalbox("Unable to load any WinSock library"); + modalfatalbox("Unable to load any WinSock library"); #ifndef NO_IPV6 /* Check if we have getaddrinfo in Winsock */ if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) { -#ifdef NET_SETUP_DIAGNOSTICS - logevent(NULL, "Native WinSock IPv6 support detected"); -#endif GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo); GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo); GET_WINDOWS_FUNCTION(winsock_module, getnameinfo); - GET_WINDOWS_FUNCTION(winsock_module, gai_strerror); + /* 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) { -#ifdef NET_SETUP_DIAGNOSTICS - logevent(NULL, "WSH IPv6 support detected"); -#endif GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); GET_WINDOWS_FUNCTION(wship6_module, getnameinfo); - GET_WINDOWS_FUNCTION(wship6_module, gai_strerror); + /* See comment above about type check */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); } else { -#ifdef NET_SETUP_DIAGNOSTICS - logevent(NULL, "No IPv6 support detected"); -#endif } } GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA); -#else -#ifdef NET_SETUP_DIAGNOSTICS - logevent(NULL, "PuTTY was built without IPv6 support"); -#endif #endif GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect); GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect); - GET_WINDOWS_FUNCTION(winsock_module, select); + /* 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); +#ifndef COVERITY GET_WINDOWS_FUNCTION(winsock_module, ntohl); GET_WINDOWS_FUNCTION(winsock_module, htonl); GET_WINDOWS_FUNCTION(winsock_module, htons); GET_WINDOWS_FUNCTION(winsock_module, ntohs); - GET_WINDOWS_FUNCTION(winsock_module, gethostname); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname); +#else + /* The toolchain I use for Windows Coverity builds doesn't know + * the type signatures of these */ + 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); +#endif 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); +#if (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__ + /* Older Visual Studio, and MinGW as of Ubuntu 16.04, don't know + * about this function at all, so can't type-check it */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, inet_ntop); +#else GET_WINDOWS_FUNCTION(winsock_module, inet_ntop); +#endif GET_WINDOWS_FUNCTION(winsock_module, connect); GET_WINDOWS_FUNCTION(winsock_module, bind); GET_WINDOWS_FUNCTION(winsock_module, setsockopt); @@ -328,7 +322,7 @@ void sk_init(void) if (!sk_startup(2,2) && !sk_startup(2,0) && !sk_startup(1,1)) { - fatalbox("Unable to initialise WinSock"); + modalfatalbox("Unable to initialise WinSock"); } sktree = newtree234(cmpfortree); @@ -336,7 +330,7 @@ void sk_init(void) void sk_cleanup(void) { - Actual_Socket s; + NetSocket *s; int i; if (sktree) { @@ -357,34 +351,8 @@ void sk_cleanup(void) #endif } -struct errstring { - int error; - char *text; -}; - -static int errstring_find(void *av, void *bv) -{ - int *a = (int *)av; - struct errstring *b = (struct errstring *)bv; - if (*a < b->error) - return -1; - if (*a > b->error) - return +1; - return 0; -} -static int errstring_compare(void *av, void *bv) -{ - struct errstring *a = (struct errstring *)av; - return errstring_find(&a->error, bv); -} - -static tree234 *errstrings = NULL; - const char *winsock_error_string(int error) { - const char prefix[] = "Network error: "; - struct errstring *es; - /* * Error codes we know about and have historically had reasonably * sensible error messages for. @@ -464,56 +432,15 @@ const char *winsock_error_string(int error) } /* - * Generic code to handle any other error. - * - * Slightly nasty hack here: we want to return a static string - * which the caller will never have to worry about freeing, but on - * the other hand if we call FormatMessage to get it then it will - * want to either allocate a buffer or write into one we own. - * - * So what we do is to maintain a tree234 of error strings we've - * already used. New ones are allocated from the heap, but then - * put in this tree and kept forever. + * Handle any other error code by delegating to win_strerror. */ - - if (!errstrings) - errstrings = newtree234(errstring_compare); - - es = find234(errstrings, &error, errstring_find); - - if (!es) { - int bufsize, bufused; - - es = snew(struct errstring); - es->error = error; - /* maximum size for FormatMessage is 64K */ - bufsize = 65535 + sizeof(prefix); - es->text = snewn(bufsize, char); - strcpy(es->text, prefix); - bufused = strlen(es->text); - if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - es->text + bufused, bufsize - bufused, NULL)) { - sprintf(es->text + bufused, - "Windows error code %d (and FormatMessage returned %u)", - error, (unsigned int)GetLastError()); - } else { - int len = strlen(es->text); - if (len > 0 && es->text[len-1] == '\n') - es->text[len-1] = '\0'; - } - es->text = sresize(es->text, strlen(es->text) + 1, char); - add234(errstrings, es); - } - - return es->text; + return win_strerror(error); } -SockAddr sk_namelookup(const char *host, char **canonicalname, - int address_family) +SockAddr *sk_namelookup(const char *host, char **canonicalname, + int address_family) { - SockAddr ret = snew(struct SockAddr_tag); + SockAddr *ret = snew(SockAddr); unsigned long a; char realhost[8192]; int hint_family; @@ -526,28 +453,25 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, AF_UNSPEC); /* Clear the structure and default to IPv4. */ - memset(ret, 0, sizeof(struct SockAddr_tag)); + memset(ret, 0, sizeof(SockAddr)); #ifndef NO_IPV6 ret->ais = NULL; #endif - ret->namedpipe = FALSE; + ret->namedpipe = false; ret->addresses = NULL; - ret->resolved = FALSE; + ret->resolved = false; ret->refcount = 1; *realhost = '\0'; if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) { struct hostent *h = NULL; - int err; + int err = 0; #ifndef NO_IPV6 /* * Use getaddrinfo when it's available */ if (p_getaddrinfo) { struct addrinfo hints; -#ifdef NET_SETUP_DIAGNOSTICS - logevent(NULL, "Using getaddrinfo() for resolving"); -#endif memset(&hints, 0, sizeof(hints)); hints.ai_family = hint_family; hints.ai_flags = AI_CANONNAME; @@ -558,19 +482,16 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, sfree(trimmed_host); } if (err == 0) - ret->resolved = TRUE; + ret->resolved = true; } else #endif { -#ifdef NET_SETUP_DIAGNOSTICS - logevent(NULL, "Using gethostbyname() for resolving"); -#endif /* * Otherwise use the IPv4-only gethostbyname... * (NOTE: we don't use gethostbyname as a fallback!) */ if ( (h = p_gethostbyname(host)) ) - ret->resolved = TRUE; + ret->resolved = true; else err = p_WSAGetLastError(); } @@ -626,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->resolved = true; strncpy(realhost, host, sizeof(realhost)); } realhost[lenof(realhost)-1] = '\0'; @@ -635,15 +556,15 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, return ret; } -SockAddr sk_nonamelookup(const char *host) +SockAddr *sk_nonamelookup(const char *host) { - SockAddr ret = snew(struct SockAddr_tag); + SockAddr *ret = snew(SockAddr); ret->error = NULL; - ret->resolved = FALSE; + ret->resolved = false; #ifndef NO_IPV6 ret->ais = NULL; #endif - ret->namedpipe = FALSE; + ret->namedpipe = false; ret->addresses = NULL; ret->naddresses = 0; ret->refcount = 1; @@ -652,15 +573,15 @@ SockAddr sk_nonamelookup(const char *host) return ret; } -SockAddr sk_namedpipe_addr(const char *pipename) +SockAddr *sk_namedpipe_addr(const char *pipename) { - SockAddr ret = snew(struct SockAddr_tag); + SockAddr *ret = snew(SockAddr); ret->error = NULL; - ret->resolved = FALSE; + ret->resolved = false; #ifndef NO_IPV6 ret->ais = NULL; #endif - ret->namedpipe = TRUE; + ret->namedpipe = true; ret->addresses = NULL; ret->naddresses = 0; ret->refcount = 1; @@ -669,26 +590,26 @@ SockAddr sk_namedpipe_addr(const char *pipename) return ret; } -int sk_nextaddr(SockAddr addr, SockAddrStep *step) +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; + return true; } else - return FALSE; + return false; } #endif if (step->curraddr+1 < addr->naddresses) { step->curraddr++; - return TRUE; + return true; } else { - return FALSE; + return false; } } -void sk_getaddr(SockAddr addr, char *buf, int buflen) +void sk_getaddr(SockAddr *addr, char *buf, int buflen) { SockAddrStep step; START_STEP(addr, step); @@ -731,10 +652,10 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen) * rather than dynamically allocated - that should clue in anyone * writing a call to it that something is weird about it.) */ -static struct SockAddr_tag sk_extractaddr_tmp( - SockAddr addr, const SockAddrStep *step) +static SockAddr sk_extractaddr_tmp( + SockAddr *addr, const SockAddrStep *step) { - struct SockAddr_tag toret; + SockAddr toret; toret = *addr; /* structure copy */ toret.refcount = 1; @@ -751,12 +672,12 @@ static struct SockAddr_tag sk_extractaddr_tmp( return toret; } -int sk_addr_needs_port(SockAddr addr) +bool sk_addr_needs_port(SockAddr *addr) { - return addr->namedpipe ? FALSE : TRUE; + return !addr->namedpipe; } -int sk_hostname_is_local(const char *name) +bool sk_hostname_is_local(const char *name) { return !strcmp(name, "localhost") || !strcmp(name, "::1") || @@ -766,10 +687,10 @@ int sk_hostname_is_local(const char *name) static INTERFACE_INFO local_interfaces[16]; static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */ -static int ipv4_is_local_addr(struct in_addr addr) +static bool ipv4_is_local_addr(struct in_addr addr) { if (ipv4_is_loopback(addr)) - return 1; /* loopback addresses are local */ + return true; /* loopback addresses are local */ if (!n_local_interfaces) { SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0); DWORD retbytes; @@ -782,7 +703,7 @@ static int ipv4_is_local_addr(struct in_addr addr) &retbytes, NULL, NULL) == 0) n_local_interfaces = retbytes / sizeof(INTERFACE_INFO); else - logevent(NULL, "Unable to get list of local IP addresses"); + n_local_interfaces = -1; } if (n_local_interfaces > 0) { int i; @@ -790,13 +711,13 @@ static int ipv4_is_local_addr(struct in_addr addr) SOCKADDR_IN *address = (SOCKADDR_IN *)&local_interfaces[i].iiAddress; if (address->sin_addr.s_addr == addr.s_addr) - return 1; /* this address is local */ + return true; /* this address is local */ } } - return 0; /* this address is not local */ + return false; /* this address is not local */ } -int sk_address_is_local(SockAddr addr) +bool sk_address_is_local(SockAddr *addr) { SockAddrStep step; int family; @@ -823,16 +744,16 @@ int sk_address_is_local(SockAddr addr) } } else { assert(family == AF_UNSPEC); - return 0; /* we don't know; assume not */ + return false; /* we don't know; assume not */ } } -int sk_address_is_special_local(SockAddr addr) +bool sk_address_is_special_local(SockAddr *addr) { - return 0; /* no Unix-domain socket analogue here */ + return false; /* no Unix-domain socket analogue here */ } -int sk_addrtype(SockAddr addr) +int sk_addrtype(SockAddr *addr) { SockAddrStep step; int family; @@ -846,7 +767,7 @@ int sk_addrtype(SockAddr addr) ADDRTYPE_NAME); } -void sk_addrcopy(SockAddr addr, char *buf) +void sk_addrcopy(SockAddr *addr, char *buf) { SockAddrStep step; int family; @@ -863,7 +784,7 @@ void sk_addrcopy(SockAddr addr, char *buf) memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr, sizeof(struct in6_addr)); else - assert(FALSE); + assert(false); } else #endif if (family == AF_INET) { @@ -874,7 +795,7 @@ void sk_addrcopy(SockAddr addr, char *buf) } } -void sk_addr_free(SockAddr addr) +void sk_addr_free(SockAddr *addr) { if (--addr->refcount > 0) return; @@ -887,22 +808,22 @@ void sk_addr_free(SockAddr addr) sfree(addr); } -SockAddr sk_addr_dup(SockAddr addr) +SockAddr *sk_addr_dup(SockAddr *addr) { addr->refcount++; return addr; } -static Plug sk_tcp_plug(Socket sock, Plug p) +static Plug *sk_net_plug(Socket *sock, Plug *p) { - Actual_Socket s = (Actual_Socket) sock; - Plug ret = s->plug; + NetSocket *s = container_of(sock, NetSocket, sock); + Plug *ret = s->plug; if (p) s->plug = p; return ret; } -static void sk_tcp_flush(Socket s) +static void sk_net_flush(Socket *s) { /* * We send data to the socket as soon as we can anyway, @@ -910,48 +831,46 @@ static void sk_tcp_flush(Socket s) */ } -static void sk_tcp_close(Socket s); -static int sk_tcp_write(Socket s, const char *data, int len); -static int sk_tcp_write_oob(Socket s, const char *data, int len); -static void sk_tcp_write_eof(Socket s); -static void sk_tcp_set_frozen(Socket s, int is_frozen); -static const char *sk_tcp_socket_error(Socket s); -static char *sk_tcp_peer_info(Socket s); - -extern char *do_select(SOCKET skt, int startup); +static void sk_net_close(Socket *s); +static int sk_net_write(Socket *s, const void *data, int len); +static int sk_net_write_oob(Socket *s, const void *data, int 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 = { + sk_net_plug, + sk_net_close, + sk_net_write, + sk_net_write_oob, + sk_net_write_eof, + sk_net_flush, + sk_net_set_frozen, + sk_net_socket_error, + sk_net_peer_info, +}; -static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug) +static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) { - static const struct socket_function_table fn_table = { - sk_tcp_plug, - sk_tcp_close, - sk_tcp_write, - sk_tcp_write_oob, - sk_tcp_write_eof, - sk_tcp_flush, - sk_tcp_set_frozen, - sk_tcp_socket_error, - sk_tcp_peer_info, - }; - DWORD err; char *errstr; - Actual_Socket ret; + NetSocket *ret; /* - * Create Socket structure. + * Create NetSocket structure. */ - ret = snew(struct Socket_tag); - ret->fn = &fn_table; + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); - ret->writable = 1; /* to start with */ + ret->writable = true; /* to start with */ ret->sending_oob = 0; ret->outgoingeof = EOF_NO; - ret->frozen = 1; - ret->frozen_readable = 0; - ret->localhost_only = 0; /* unused, but best init anyway */ + ret->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; @@ -961,25 +880,25 @@ static Socket sk_tcp_accept(accept_ctx_t ctx, Plug plug) if (ret->s == INVALID_SOCKET) { err = p_WSAGetLastError(); ret->error = winsock_error_string(err); - return (Socket) ret; + return &ret->sock; } - ret->oobinline = 0; + 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, 1); + errstr = do_select(ret->s, true); if (errstr) { ret->error = errstr; - return (Socket) ret; + return &ret->sock; } add234(sktree, ret); - return (Socket) ret; + return &ret->sock; } -static DWORD try_connect(Actual_Socket sock) +static DWORD try_connect(NetSocket *sock) { SOCKET s; #ifndef NO_IPV6 @@ -992,12 +911,12 @@ static DWORD try_connect(Actual_Socket sock) int family; if (sock->s != INVALID_SOCKET) { - do_select(sock->s, 0); + do_select(sock->s, false); p_closesocket(sock->s); } { - struct SockAddr_tag thisaddr = sk_extractaddr_tmp( + SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); plug_log(sock->plug, 0, &thisaddr, sock->port, NULL, 0); } @@ -1027,17 +946,17 @@ static DWORD try_connect(Actual_Socket sock) SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); if (sock->oobinline) { - BOOL b = TRUE; + BOOL b = true; p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b)); } if (sock->nodelay) { - BOOL b = TRUE; + BOOL b = true; p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b)); } if (sock->keepalive) { - BOOL b = TRUE; + BOOL b = true; p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b)); } @@ -1124,7 +1043,7 @@ static DWORD try_connect(Actual_Socket 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, 1); + errstr = do_select(s, true); if (errstr) { sock->error = errstr; err = 1; @@ -1157,7 +1076,7 @@ static DWORD try_connect(Actual_Socket sock) * If we _don't_ get EWOULDBLOCK, the connect has completed * and we should set the socket as writable. */ - sock->writable = 1; + sock->writable = true; } err = 0; @@ -1170,46 +1089,34 @@ static DWORD try_connect(Actual_Socket sock) add234(sktree, sock); if (err) { - struct SockAddr_tag thisaddr = sk_extractaddr_tmp( + SockAddr thisaddr = sk_extractaddr_tmp( sock->addr, &sock->step); plug_log(sock->plug, 1, &thisaddr, sock->port, sock->error, err); } return err; } -Socket sk_new(SockAddr addr, int port, int privport, int oobinline, - int nodelay, int keepalive, Plug plug) +Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, + bool nodelay, bool keepalive, Plug *plug) { - static const struct socket_function_table fn_table = { - sk_tcp_plug, - sk_tcp_close, - sk_tcp_write, - sk_tcp_write_oob, - sk_tcp_write_eof, - sk_tcp_flush, - sk_tcp_set_frozen, - sk_tcp_socket_error, - sk_tcp_peer_info, - }; - - Actual_Socket ret; + NetSocket *ret; DWORD err; /* - * Create Socket structure. + * Create NetSocket structure. */ - ret = snew(struct Socket_tag); - ret->fn = &fn_table; + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); - ret->connected = 0; /* to start with */ - ret->writable = 0; /* to start with */ + ret->connected = false; /* to start with */ + ret->writable = false; /* to start with */ ret->sending_oob = 0; ret->outgoingeof = EOF_NO; - ret->frozen = 0; - ret->frozen_readable = 0; - ret->localhost_only = 0; /* unused, but best init anyway */ + ret->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; @@ -1226,24 +1133,12 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline, err = try_connect(ret); } while (err && sk_nextaddr(ret->addr, &ret->step)); - return (Socket) ret; + return &ret->sock; } -Socket sk_newlistener(const char *srcaddr, int port, Plug plug, - int local_host_only, int orig_address_family) +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) { - static const struct socket_function_table fn_table = { - sk_tcp_plug, - sk_tcp_close, - sk_tcp_write, - sk_tcp_write_oob, - sk_tcp_write_eof, - sk_tcp_flush, - sk_tcp_set_frozen, - sk_tcp_socket_error, - sk_tcp_peer_info, - }; - SOCKET s; #ifndef NO_IPV6 SOCKADDR_IN6 a6; @@ -1252,25 +1147,25 @@ Socket sk_newlistener(const char *srcaddr, int port, Plug plug, DWORD err; char *errstr; - Actual_Socket ret; + NetSocket *ret; int retcode; int on = 1; int address_family; /* - * Create Socket structure. + * Create NetSocket structure. */ - ret = snew(struct Socket_tag); - ret->fn = &fn_table; + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; ret->error = NULL; ret->plug = plug; bufchain_init(&ret->output_data); - ret->writable = 0; /* to start with */ + ret->writable = false; /* to start with */ ret->sending_oob = 0; ret->outgoingeof = EOF_NO; - ret->frozen = 0; - ret->frozen_readable = 0; + ret->frozen = false; + ret->frozen_readable = false; ret->localhost_only = local_host_only; ret->pending_error = 0; ret->parent = ret->child = NULL; @@ -1304,12 +1199,12 @@ Socket sk_newlistener(const char *srcaddr, int port, Plug plug, if (s == INVALID_SOCKET) { err = p_WSAGetLastError(); ret->error = winsock_error_string(err); - return (Socket) ret; + return &ret->sock; } - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); - ret->oobinline = 0; + ret->oobinline = false; p_setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); @@ -1344,7 +1239,7 @@ Socket sk_newlistener(const char *srcaddr, int port, Plug plug, } else #endif { - int got_addr = 0; + bool got_addr = false; a.sin_family = AF_INET; /* @@ -1356,7 +1251,7 @@ Socket sk_newlistener(const char *srcaddr, int port, Plug plug, if (a.sin_addr.s_addr != INADDR_NONE) { /* Override localhost_only with specified listen addr. */ ret->localhost_only = ipv4_is_loopback(a.sin_addr); - got_addr = 1; + got_addr = true; } } @@ -1390,23 +1285,23 @@ Socket sk_newlistener(const char *srcaddr, int port, Plug plug, if (err) { p_closesocket(s); ret->error = winsock_error_string(err); - return (Socket) ret; + return &ret->sock; } if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { p_closesocket(s); ret->error = winsock_error_string(p_WSAGetLastError()); - return (Socket) ret; + 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, 1); + errstr = do_select(s, true); if (errstr) { p_closesocket(s); ret->error = errstr; - return (Socket) ret; + return &ret->sock; } add234(sktree, ret); @@ -1417,35 +1312,33 @@ Socket sk_newlistener(const char *srcaddr, int port, Plug plug, * IPv6 listening socket and link it to this one. */ if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) { - Actual_Socket other; - - other = (Actual_Socket) sk_newlistener(srcaddr, port, plug, - local_host_only, ADDRTYPE_IPV6); + Socket *other = sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV6); if (other) { - if (!other->error) { - other->parent = ret; - ret->child = other; + NetSocket *ns = container_of(other, NetSocket, sock); + if (!ns->error) { + ns->parent = ret; + ret->child = ns; } else { - sfree(other); + sfree(ns); } } } #endif - return (Socket) ret; + return &ret->sock; } -static void sk_tcp_close(Socket sock) +static void sk_net_close(Socket *sock) { - extern char *do_select(SOCKET skt, int startup); - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); if (s->child) - sk_tcp_close((Socket)s->child); + sk_net_close(&s->child->sock); del234(sktree, s); - do_select(s->s, 0); + do_select(s->s, false); p_closesocket(s->s); if (s->addr) sk_addr_free(s->addr); @@ -1457,7 +1350,7 @@ static void sk_tcp_close(Socket sock) */ static void socket_error_callback(void *vs) { - Actual_Socket s = (Actual_Socket)vs; + NetSocket *s = (NetSocket *)vs; /* * Just in case other socket work has caused this socket to vanish @@ -1477,7 +1370,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(Actual_Socket s) +void try_send(NetSocket *s) { while (s->sending_oob || bufchain_size(&s->output_data) > 0) { int nsent; @@ -1507,28 +1400,22 @@ void try_send(Actual_Socket s) * a small number - so we check that case and treat * it just like WSAEWOULDBLOCK.) */ - s->writable = FALSE; + s->writable = false; return; - } else if (nsent == 0 || - err == WSAECONNABORTED || err == WSAECONNRESET) { + } else { /* - * If send() returns CONNABORTED or CONNRESET, we - * unfortunately can't just call plug_closing(), - * because it's quite likely that we're currently - * _in_ a call from the code we'd be calling back - * to, so we'd have to make half the SSH code - * reentrant. Instead we flag a pending error on - * the socket, to be dealt with (by calling - * plug_closing()) at some suitable future moment. + * 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 { - /* We're inside the Windows frontend here, so we know - * that the frontend handle is unnecessary. */ - logevent(NULL, winsock_error_string(err)); - fatalbox("%s", winsock_error_string(err)); } } else { if (s->sending_oob) { @@ -1554,9 +1441,9 @@ void try_send(Actual_Socket s) } } -static int sk_tcp_write(Socket sock, const char *buf, int len) +static int sk_net_write(Socket *sock, const void *buf, int len) { - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); assert(s->outgoingeof == EOF_NO); @@ -1574,9 +1461,9 @@ static int sk_tcp_write(Socket sock, const char *buf, int len) return bufchain_size(&s->output_data); } -static int sk_tcp_write_oob(Socket sock, const char *buf, int len) +static int sk_net_write_oob(Socket *sock, const void *buf, int len) { - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); assert(s->outgoingeof == EOF_NO); @@ -1597,9 +1484,9 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len) return s->sending_oob; } -static void sk_tcp_write_eof(Socket sock) +static void sk_net_write_eof(Socket *sock) { - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); assert(s->outgoingeof == EOF_NO); @@ -1615,22 +1502,22 @@ static void sk_tcp_write_eof(Socket sock) try_send(s); } -int select_result(WPARAM wParam, LPARAM lParam) +void select_result(WPARAM wParam, LPARAM lParam) { - int ret, open; + int ret; DWORD err; char buf[20480]; /* nice big buffer for plenty of speed */ - Actual_Socket s; - u_long atmark; + NetSocket *s; + bool atmark; /* wParam is the socket itself */ if (wParam == 0) - return 1; /* boggle */ + return; /* boggle */ s = find234(sktree, (void *) wParam, cmpforsearch); if (!s) - return 1; /* boggle */ + return; /* boggle */ if ((err = WSAGETSELECTERROR(lParam)) != 0) { /* @@ -1638,7 +1525,7 @@ int select_result(WPARAM wParam, LPARAM lParam) * plug. */ if (s->addr) { - struct SockAddr_tag thisaddr = sk_extractaddr_tmp( + SockAddr thisaddr = sk_extractaddr_tmp( s->addr, &s->step); plug_log(s->plug, 1, &thisaddr, s->port, winsock_error_string(err), err); @@ -1647,16 +1534,16 @@ int select_result(WPARAM wParam, LPARAM lParam) } } if (err != 0) - return plug_closing(s->plug, winsock_error_string(err), err, 0); - else - return 1; + plug_closing(s->plug, winsock_error_string(err), err, 0); + return; } noise_ultralight(lParam); switch (WSAGETSELECTEVENT(lParam)) { case FD_CONNECT: - s->connected = s->writable = 1; + s->connected = true; + s->writable = true; /* * Once a socket is connected, we can stop falling * back through the candidate addresses to connect @@ -1670,7 +1557,7 @@ int select_result(WPARAM wParam, LPARAM lParam) case FD_READ: /* In the case the socket is still frozen, we don't even bother */ if (s->frozen) { - s->frozen_readable = 1; + s->frozen_readable = true; break; } @@ -1681,8 +1568,8 @@ int select_result(WPARAM wParam, LPARAM lParam) * (data prior to urgent). */ if (s->oobinline) { - atmark = 1; - p_ioctlsocket(s->s, SIOCATMARK, &atmark); + 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 @@ -1690,8 +1577,9 @@ int select_result(WPARAM wParam, LPARAM lParam) * 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 = 1; + atmark = true; ret = p_recv(s->s, buf, sizeof(buf), 0); noise_ultralight(ret); @@ -1702,12 +1590,11 @@ int select_result(WPARAM wParam, LPARAM lParam) } } if (ret < 0) { - return plug_closing(s->plug, winsock_error_string(err), err, - 0); + plug_closing(s->plug, winsock_error_string(err), err, 0); } else if (0 == ret) { - return plug_closing(s->plug, NULL, 0, 0); + plug_closing(s->plug, NULL, 0, 0); } else { - return plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + plug_receive(s->plug, atmark ? 0 : 1, buf, ret); } break; case FD_OOB: @@ -1720,20 +1607,16 @@ int select_result(WPARAM wParam, LPARAM lParam) ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB); noise_ultralight(ret); if (ret <= 0) { - const char *str = (ret == 0 ? "Internal networking trouble" : - winsock_error_string(p_WSAGetLastError())); - /* We're inside the Windows frontend here, so we know - * that the frontend handle is unnecessary. */ - logevent(NULL, str); - fatalbox("%s", str); + int err = p_WSAGetLastError(); + plug_closing(s->plug, winsock_error_string(err), err, 0); } else { - return plug_receive(s->plug, 2, buf, ret); + plug_receive(s->plug, 2, buf, ret); } break; case FD_WRITE: { int bufsize_before, bufsize_after; - s->writable = 1; + 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); @@ -1743,23 +1626,21 @@ int select_result(WPARAM wParam, LPARAM lParam) break; case FD_CLOSE: /* Signal a close on the socket. First read any outstanding data. */ - open = 1; do { ret = p_recv(s->s, buf, sizeof(buf), 0); if (ret < 0) { err = p_WSAGetLastError(); if (err == WSAEWOULDBLOCK) break; - return plug_closing(s->plug, winsock_error_string(err), - err, 0); + plug_closing(s->plug, winsock_error_string(err), err, 0); } else { if (ret) - open &= plug_receive(s->plug, 0, buf, ret); + plug_receive(s->plug, 0, buf, ret); else - open &= plug_closing(s->plug, NULL, 0, 0); + plug_closing(s->plug, NULL, 0, 0); } } while (ret > 0); - return open; + return; case FD_ACCEPT: { #ifdef NO_IPV6 @@ -1792,13 +1673,11 @@ int select_result(WPARAM wParam, LPARAM lParam) #endif { p_closesocket(t); /* dodgy WinSock let nonlocal through */ - } else if (plug_accepting(s->plug, sk_tcp_accept, actx)) { + } else if (plug_accepting(s->plug, sk_net_accept, actx)) { p_closesocket(t); /* denied or error */ } } } - - return 1; } /* @@ -1806,19 +1685,19 @@ int select_result(WPARAM wParam, LPARAM lParam) * 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) +const char *sk_addr_error(SockAddr *addr) { return addr->error; } -static const char *sk_tcp_socket_error(Socket sock) +static const char *sk_net_socket_error(Socket *sock) { - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); return s->error; } -static char *sk_tcp_peer_info(Socket sock) +static SocketPeerInfo *sk_net_peer_info(Socket *sock) { - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); #ifdef NO_IPV6 struct sockaddr_in addr; #else @@ -1826,52 +1705,69 @@ static char *sk_tcp_peer_info(Socket sock) 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) { - return dupprintf - ("%s:%d", - p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr), - (int)p_ntohs(((struct sockaddr_in *)&addr)->sin_port)); + 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) { - return dupprintf - ("[%s]:%d", - p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr, - buf, sizeof(buf)), - (int)p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port)); + 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_tcp_set_frozen(Socket sock, int is_frozen) +static void sk_net_set_frozen(Socket *sock, bool is_frozen) { - Actual_Socket s = (Actual_Socket) sock; + NetSocket *s = container_of(sock, NetSocket, sock); if (s->frozen == is_frozen) return; s->frozen = is_frozen; if (!is_frozen) { - do_select(s->s, 1); + do_select(s->s, true); if (s->frozen_readable) { char c; p_recv(s->s, &c, 1, MSG_PEEK); } } - s->frozen_readable = 0; + s->frozen_readable = false; } void socket_reselect_all(void) { - Actual_Socket s; + NetSocket *s; int i; for (i = 0; (s = index234(sktree, i)) != NULL; i++) { if (!s->frozen) - do_select(s->s, 1); + do_select(s->s, true); } } @@ -1880,7 +1776,7 @@ void socket_reselect_all(void) */ SOCKET first_socket(int *state) { - Actual_Socket s; + NetSocket *s; *state = 0; s = index234(sktree, (*state)++); return s ? s->s : INVALID_SOCKET; @@ -1888,18 +1784,18 @@ SOCKET first_socket(int *state) SOCKET next_socket(int *state) { - Actual_Socket s = index234(sktree, (*state)++); + NetSocket *s = index234(sktree, (*state)++); return s ? s->s : INVALID_SOCKET; } -extern int socket_writable(SOCKET skt) +bool socket_writable(SOCKET skt) { - Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch); + NetSocket *s = find234(sktree, (void *)skt, cmpforsearch); if (s) return bufchain_size(&s->output_data) > 0; else - return 0; + return false; } int net_service_lookup(char *service) @@ -1928,11 +1824,11 @@ char *get_hostname(void) return hostname; } -SockAddr platform_get_x11_unix_address(const char *display, int displaynum, +SockAddr *platform_get_x11_unix_address(const char *display, int displaynum, char **canonicalname) { - SockAddr ret = snew(struct SockAddr_tag); - memset(ret, 0, sizeof(struct SockAddr_tag)); + 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/winnoise.c b/windows/winnoise.c index 31364546..39c2049a 100644 --- a/windows/winnoise.c +++ b/windows/winnoise.c @@ -19,6 +19,29 @@ 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. */ @@ -28,8 +51,8 @@ void noise_get_heavy(void (*func) (void *, int)) HANDLE srch; WIN32_FIND_DATA finddata; DWORD pid; - HCRYPTPROV crypt_provider; char winpath[MAX_PATH + 3]; + BYTE buf[32]; GetWindowsDirectory(winpath, sizeof(winpath)); strcat(winpath, "\\*"); @@ -44,22 +67,9 @@ void noise_get_heavy(void (*func) (void *, int)) pid = GetCurrentProcessId(); func(&pid, sizeof(pid)); - if (!wincrypt_module) { - wincrypt_module = load_system32_dll("advapi32.dll"); - GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA); - GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom); - GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext); - } - - if (wincrypt_module && p_CryptAcquireContextA && - p_CryptGenRandom && p_CryptReleaseContext && - p_CryptAcquireContextA(&crypt_provider, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) { - BYTE buf[32]; - if (p_CryptGenRandom(crypt_provider, 32, buf)) { - func(buf, sizeof(buf)); - } - p_CryptReleaseContext(crypt_provider, 0); + if (win_read_random(buf, sizeof(buf))) { + func(buf, sizeof(buf)); + smemclr(buf, sizeof(buf)); } read_random_seed(func); diff --git a/windows/winnpc.c b/windows/winnpc.c index e5d1d7a6..4fcb9e26 100644 --- a/windows/winnpc.c +++ b/windows/winnpc.c @@ -5,7 +5,6 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "tree234.h" #include "putty.h" #include "network.h" @@ -16,16 +15,11 @@ #include "winsecur.h" -Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug plug, int overlapped); - -Socket new_named_pipe_client(const char *pipename, Plug plug) +Socket *new_named_pipe_client(const char *pipename, Plug *plug) { HANDLE pipehandle; PSID usersid, pipeowner; PSECURITY_DESCRIPTOR psd; - char *err; - Socket ret; assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); assert(strchr(pipename + 9, '\\') == NULL); @@ -39,11 +33,9 @@ Socket new_named_pipe_client(const char *pipename, Plug plug) break; if (GetLastError() != ERROR_PIPE_BUSY) { - err = dupprintf("Unable to open named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - ret = new_error_socket(err, plug); - sfree(err); - return ret; + return new_error_socket_fmt( + plug, "Unable to open named pipe '%s': %s", + pipename, win_strerror(GetLastError())); } /* @@ -54,46 +46,38 @@ Socket new_named_pipe_client(const char *pipename, Plug plug) * take excessively long.) */ if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { - err = dupprintf("Error waiting for named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - ret = new_error_socket(err, plug); - sfree(err); - return ret; + return new_error_socket_fmt( + plug, "Error waiting for named pipe '%s': %s", + pipename, win_strerror(GetLastError())); } } if ((usersid = get_user_sid()) == NULL) { CloseHandle(pipehandle); - err = dupprintf("Unable to get user SID"); - ret = new_error_socket(err, plug); - sfree(err); - return ret; + return new_error_socket_fmt( + plug, "Unable to get user SID: %s", win_strerror(GetLastError())); } if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, &pipeowner, NULL, NULL, NULL, &psd) != ERROR_SUCCESS) { - err = dupprintf("Unable to get named pipe security information: %s", - win_strerror(GetLastError())); - ret = new_error_socket(err, plug); - sfree(err); CloseHandle(pipehandle); - return ret; + return new_error_socket_fmt( + plug, "Unable to get named pipe security information: %s", + win_strerror(GetLastError())); } if (!EqualSid(pipeowner, usersid)) { - err = dupprintf("Owner of named pipe '%s' is not us", pipename); - ret = new_error_socket(err, plug); - sfree(err); CloseHandle(pipehandle); LocalFree(psd); - return ret; + return new_error_socket_fmt( + plug, "Owner of named pipe '%s' is not us", pipename); } LocalFree(psd); - return make_handle_socket(pipehandle, pipehandle, NULL, plug, TRUE); + return make_handle_socket(pipehandle, pipehandle, NULL, plug, true); } #endif /* !defined NO_SECURITY */ diff --git a/windows/winnps.c b/windows/winnps.c index f992a4f0..fa1d804b 100644 --- a/windows/winnps.c +++ b/windows/winnps.c @@ -5,7 +5,6 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "tree234.h" #include "putty.h" #include "network.h" @@ -16,14 +15,7 @@ #include "winsecur.h" -Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug plug, int overlapped); - -typedef struct Socket_named_pipe_server_tag *Named_Pipe_Server_Socket; -struct Socket_named_pipe_server_tag { - const struct socket_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ - +typedef struct NamedPipeServerSocket { /* Parameters for (repeated) creation of named pipe objects */ PSECURITY_DESCRIPTOR psd; PACL acl; @@ -35,22 +27,24 @@ struct Socket_named_pipe_server_tag { struct handle *callback_handle; /* winhandl.c's reference */ /* PuTTY Socket machinery */ - Plug plug; + Plug *plug; char *error; -}; -static Plug sk_namedpipeserver_plug(Socket s, Plug p) + Socket sock; +} NamedPipeServerSocket; + +static Plug *sk_namedpipeserver_plug(Socket *s, Plug *p) { - Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; - Plug ret = ps->plug; + 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) +static void sk_namedpipeserver_close(Socket *s) { - Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); if (ps->callback_handle) handle_free(ps->callback_handle); @@ -65,25 +59,25 @@ static void sk_namedpipeserver_close(Socket s) sfree(ps); } -static const char *sk_namedpipeserver_socket_error(Socket s) +static const char *sk_namedpipeserver_socket_error(Socket *s) { - Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); return ps->error; } -static char *sk_namedpipeserver_peer_info(Socket s) +static SocketPeerInfo *sk_namedpipeserver_peer_info(Socket *s) { return NULL; } -static int create_named_pipe(Named_Pipe_Server_Socket ps, int first_instance) +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; + sa.bInheritHandle = false; ps->pipehandle = CreateNamedPipe (/* lpName */ @@ -116,21 +110,15 @@ static int create_named_pipe(Named_Pipe_Server_Socket ps, int first_instance) return ps->pipehandle != INVALID_HANDLE_VALUE; } -static Socket named_pipe_accept(accept_ctx_t ctx, Plug plug) +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, plug, true); } -/* - * Dummy SockAddr type which just holds a named pipe address. Only - * used for calling plug_log from named_pipe_accept_loop() here. - */ -SockAddr sk_namedpipe_addr(const char *pipename); - -static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps, - int got_one_already) +static void named_pipe_accept_loop(NamedPipeServerSocket *ps, + bool got_one_already) { while (1) { int error; @@ -139,7 +127,7 @@ static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps, if (got_one_already) { /* If we were called with a connection already waiting, * skip this step. */ - got_one_already = FALSE; + got_one_already = false; error = 0; } else { /* @@ -176,7 +164,7 @@ static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps, CloseHandle(conn); } - if (!create_named_pipe(ps, FALSE)) { + if (!create_named_pipe(ps, false)) { error = GetLastError(); } else { /* @@ -198,32 +186,30 @@ static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps, static void named_pipe_connect_callback(void *vps) { - Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket)vps; - named_pipe_accept_loop(ps, TRUE); + NamedPipeServerSocket *ps = (NamedPipeServerSocket *)vps; + named_pipe_accept_loop(ps, true); } -Socket new_named_pipe_listener(const char *pipename, Plug plug) +/* + * This socket type is only used for listening, so it should never + * be asked to write or flush or set_frozen. + */ +static const SocketVtable NamedPipeServerSocket_sockvt = { + sk_namedpipeserver_plug, + sk_namedpipeserver_close, + NULL /* write */, + NULL /* write_oob */, + NULL /* write_eof */, + NULL /* flush */, + NULL /* set_frozen */, + sk_namedpipeserver_socket_error, + sk_namedpipeserver_peer_info, +}; + +Socket *new_named_pipe_listener(const char *pipename, Plug *plug) { - /* - * This socket type is only used for listening, so it should never - * be asked to write or flush or set_frozen. - */ - static const struct socket_function_table socket_fn_table = { - sk_namedpipeserver_plug, - sk_namedpipeserver_close, - NULL /* write */, - NULL /* write_oob */, - NULL /* write_eof */, - NULL /* flush */, - NULL /* set_frozen */, - sk_namedpipeserver_socket_error, - sk_namedpipeserver_peer_info, - }; - - Named_Pipe_Server_Socket ret; - - ret = snew(struct Socket_named_pipe_server_tag); - ret->fn = &socket_fn_table; + NamedPipeServerSocket *ret = snew(NamedPipeServerSocket); + ret->sock.vt = &NamedPipeServerSocket_sockvt; ret->plug = plug; ret->error = NULL; ret->psd = NULL; @@ -239,21 +225,21 @@ Socket new_named_pipe_listener(const char *pipename, Plug plug) goto cleanup; } - if (!create_named_pipe(ret, TRUE)) { + 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->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); + named_pipe_accept_loop(ret, false); cleanup: - return (Socket) ret; + return &ret->sock; } #endif /* !defined NO_SECURITY */ diff --git a/windows/winpgen.c b/windows/winpgen.c index c4f4de45..833ef393 100644 --- a/windows/winpgen.c +++ b/windows/winpgen.c @@ -60,6 +60,9 @@ void nonfatal(const char *fmt, ...) sfree(stuff); } +/* Stubs needed to link against misc.c */ +void queue_idempotent_callback(IdempotentCallback *ic) { assert(0); } + /* ---------------------------------------------------------------------- * Progress report code. This is really horrible :-) */ @@ -68,7 +71,7 @@ void nonfatal(const char *fmt, ...) struct progress { int nphases; struct { - int exponential; + bool exponential; unsigned startpoint, total; unsigned param, current, n; /* if exponential */ unsigned mult; /* if linear */ @@ -90,11 +93,11 @@ static void progress_update(void *param, int action, int phase, int iprogress) p->nphases = 0; break; case PROGFN_LIN_PHASE: - p->phases[phase-1].exponential = 0; + p->phases[phase-1].exponential = false; p->phases[phase-1].mult = p->phases[phase].total / progress; break; case PROGFN_EXP_PHASE: - p->phases[phase-1].exponential = 1; + p->phases[phase-1].exponential = true; p->phases[phase-1].param = 0x10000 + progress; p->phases[phase-1].current = p->phases[phase-1].total; p->phases[phase-1].n = 0; @@ -134,8 +137,6 @@ static void progress_update(void *param, int action, int phase, int iprogress) } } -extern const char ver[]; - struct PassphraseProcStruct { char **passphrase; char *comment; @@ -168,7 +169,7 @@ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } p = (struct PassphraseProcStruct *) lParam; @@ -209,8 +210,8 @@ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, * Prompt for a key file. Assumes the filename buffer is of size * FILENAME_MAX. */ -static int prompt_keyfile(HWND hwnd, char *dlgtitle, - char *filename, int save, int ppk) +static bool prompt_keyfile(HWND hwnd, char *dlgtitle, + char *filename, bool save, bool ppk) { OPENFILENAME of; memset(&of, 0, sizeof(of)); @@ -230,7 +231,7 @@ static int prompt_keyfile(HWND hwnd, char *dlgtitle, of.lpstrFileTitle = NULL; of.lpstrTitle = dlgtitle; of.Flags = 0; - return request_file(NULL, &of, FALSE, save); + return request_file(NULL, &of, false, save); } /* @@ -253,7 +254,7 @@ static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n")); @@ -293,7 +294,7 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } { @@ -322,7 +323,7 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, case 102: /* Load web browser */ ShellExecute(hwnd, "open", - "http://www.chiark.greenend.org.uk/~sgtatham/putty/", + "https://www.chiark.greenend.org.uk/~sgtatham/putty/", 0, 0, SW_SHOWDEFAULT); return 0; } @@ -376,12 +377,12 @@ static DWORD WINAPI generate_key_thread(void *param) } struct MainDlgState { - int collecting_entropy; - int generation_thread_exists; - int key_exists; + bool collecting_entropy; + bool generation_thread_exists; + bool key_exists; int entropy_got, entropy_required, entropy_size; int key_bits, curve_bits; - int ssh2; + bool ssh2; keytype keytype; char **commentptr; /* points to key.comment or ssh2key.comment */ struct ssh2_userkey ssh2key; @@ -394,7 +395,7 @@ struct MainDlgState { HMENU filemenu, keymenu, cvtmenu; }; -static void hidemany(HWND hwnd, const int *ids, int hideit) +static void hidemany(HWND hwnd, const int *ids, bool hideit) { while (*ids) { ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); @@ -490,9 +491,9 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) switch (status) { case 0: /* no key */ - hidemany(hwnd, nokey_ids, FALSE); - hidemany(hwnd, generating_ids, TRUE); - hidemany(hwnd, gotkey_ids, TRUE); + 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); @@ -523,9 +524,9 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) MF_GRAYED|MF_BYCOMMAND); break; case 1: /* generating key */ - hidemany(hwnd, nokey_ids, TRUE); - hidemany(hwnd, generating_ids, FALSE); - hidemany(hwnd, gotkey_ids, TRUE); + 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); @@ -556,9 +557,9 @@ void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) MF_GRAYED|MF_BYCOMMAND); break; case 2: - hidemany(hwnd, nokey_ids, TRUE); - hidemany(hwnd, generating_ids, TRUE); - hidemany(hwnd, gotkey_ids, FALSE); + 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); @@ -639,10 +640,10 @@ void ui_set_key_type(HWND hwnd, struct MainDlgState *state, int button) } void load_key_file(HWND hwnd, struct MainDlgState *state, - Filename *filename, int was_import_cmd) + Filename *filename, bool was_import_cmd) { char *passphrase; - int needs_pass; + bool needs_pass; int type, realtype; int ret; const char *errmsg = NULL; @@ -671,7 +672,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, comment = NULL; passphrase = NULL; if (realtype == SSH_KEYTYPE_SSH1) - needs_pass = rsakey_encrypted(filename, &comment); + needs_pass = rsa_ssh1_encrypted(filename, &comment); else if (realtype == SSH_KEYTYPE_SSH2) needs_pass = ssh2_userkey_encrypted(filename, &comment); else @@ -698,7 +699,8 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, passphrase = dupstr(""); if (type == SSH_KEYTYPE_SSH1) { if (realtype == type) - ret = loadrsakey(filename, &newkey1, passphrase, &errmsg); + ret = rsa_ssh1_loadkey( + filename, &newkey1, passphrase, &errmsg); else ret = import_ssh1(filename, realtype, &newkey1, passphrase, &errmsg); @@ -733,10 +735,9 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, passphrase); if (type == SSH_KEYTYPE_SSH1) { - char buf[128]; - char *savecomment; + char *fingerprint, *savecomment; - state->ssh2 = FALSE; + state->ssh2 = false; state->commentptr = &state->key.comment; state->key = newkey1; @@ -745,11 +746,11 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, */ savecomment = state->key.comment; state->key.comment = NULL; - rsa_fingerprint(buf, sizeof(buf), - &state->key); + fingerprint = rsa_ssh1_fingerprint(&state->key); state->key.comment = savecomment; + SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); + sfree(fingerprint); - SetDlgItemText(hwnd, IDC_FINGERPRINT, buf); /* * Construct a decimal representation * of the key, for pasting into @@ -761,7 +762,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, char *fp; char *savecomment; - state->ssh2 = TRUE; + state->ssh2 = true; state->commentptr = &state->ssh2key.comment; state->ssh2key = *newkey2; /* structure copy */ @@ -769,7 +770,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, savecomment = state->ssh2key.comment; state->ssh2key.comment = NULL; - fp = ssh2_fingerprint(state->ssh2key.alg, state->ssh2key.data); + fp = ssh2_fingerprint(state->ssh2key.key); state->ssh2key.comment = savecomment; SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); @@ -786,7 +787,7 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, * the key data. */ ui_set_state(hwnd, state, 2); - state->key_exists = TRUE; + state->key_exists = true; /* * If the user has imported a foreign key @@ -809,14 +810,45 @@ void load_key_file(HWND hwnd, struct MainDlgState *state, 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->key = &state->key; + params->dsskey = &state->dsskey; + + 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 generating_msg[] = - "Please wait while a key is generated..."; static const char entropy_msg[] = "Please generate some randomness by moving the mouse over the blank area."; struct MainDlgState *state; @@ -837,10 +869,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200))); state = snew(struct MainDlgState); - state->generation_thread_exists = FALSE; - state->collecting_entropy = FALSE; + state->generation_thread_exists = false; + state->collecting_entropy = false; state->entropy = NULL; - state->key_exists = FALSE; + state->key_exists = false; SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) state); { HMENU menu, menu1; @@ -901,7 +933,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } { @@ -960,7 +992,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, { int i, bits; const struct ec_curve *curve; - const struct ssh_signkey *alg; + const ssh_keyalg *alg; for (i = 0; i < n_ec_nist_curve_lengths; i++) { bits = ec_nist_curve_lengths[i]; @@ -978,7 +1010,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, endbox(&cp); } ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA); - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, FALSE); + SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); SendDlgItemMessage(hwnd, IDC_CURVE, CB_SETCURSEL, DEFAULT_CURVE_INDEX, 0); @@ -995,7 +1027,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, */ if (cmdline_keyfile) { Filename *fn = filename_from_str(cmdline_keyfile); - load_key_file(hwnd, state, fn, 0); + load_key_file(hwnd, state, fn, false); filename_free(fn); } @@ -1009,40 +1041,15 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, state->entropy_got, 0); if (state->entropy_got >= state->entropy_required) { - struct rsa_key_thread_params *params; - DWORD threadid; - /* * Seed the entropy pool */ random_add_heavynoise(state->entropy, state->entropy_size); smemclr(state->entropy, state->entropy_size); sfree(state->entropy); - state->collecting_entropy = FALSE; - - SetDlgItemText(hwnd, IDC_GENERATING, generating_msg); - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, - 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->key = &state->key; - params->dsskey = &state->dsskey; - - 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; - } + state->collecting_entropy = false; + + start_generating_key(hwnd, state); } } break; @@ -1102,8 +1109,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, 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); + state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false); if (!ok) state->key_bits = DEFAULT_KEY_BITS; { @@ -1136,7 +1145,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, if (ret != IDOK) break; state->key_bits = DEFAULT_KEY_BITS; - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, FALSE); + 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 @@ -1149,39 +1158,62 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, break; } - ui_set_state(hwnd, state, 1); - SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); - state->key_exists = FALSE; - state->collecting_entropy = TRUE; - - /* - * My brief statistical tests on mouse movements - * suggest that there are about 2.5 bits of - * randomness in the x position, 2.5 in the y - * position, and 1.7 in the message time, making - * 5.7 bits of unpredictability per mouse movement. - * However, other people have told me it's far less - * than that, so I'm going to be stupidly cautious - * and knock that down to a nice round 2. With this - * method, we require two words per mouse movement, - * so with 2 bits per mouse movement we expect 2 - * bits every 2 words. - */ if (state->keytype == RSA || state->keytype == DSA) - state->entropy_required = (state->key_bits / 2) * 2; + raw_entropy_required = (state->key_bits / 2) * 2; else if (state->keytype == ECDSA) - state->entropy_required = (state->curve_bits / 2) * 2; + raw_entropy_required = (state->curve_bits / 2) * 2; else - state->entropy_required = 256; - - state->entropy_got = 0; - state->entropy_size = (state->entropy_required * - sizeof(unsigned)); - state->entropy = snewn(state->entropy_required, unsigned); + raw_entropy_required = 256; + + raw_entropy_buf = snewn(raw_entropy_required, unsigned char); + if (win_read_random(raw_entropy_buf, raw_entropy_required)) { + /* + * If we can get the entropy we need from + * CryptGenRandom, just do that, and go straight + * to the key-generation phase. + */ + random_add_heavynoise(raw_entropy_buf, + raw_entropy_required); + start_generating_key(hwnd, state); + } else { + /* + * 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); + } - 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: @@ -1246,7 +1278,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, } } if (prompt_keyfile(hwnd, "Save private key as:", - filename, 1, (type == realtype))) { + filename, true, (type == realtype))) { int ret; FILE *fp = fopen(filename, "r"); if (fp) { @@ -1279,8 +1311,9 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, ret = export_ssh1(fn, type, &state->key, *passphrase ? passphrase : NULL); else - ret = saversakey(fn, &state->key, - *passphrase ? passphrase : NULL); + ret = rsa_ssh1_savekey( + fn, &state->key, + *passphrase ? passphrase : NULL); filename_free(fn); } if (ret <= 0) { @@ -1299,7 +1332,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, if (state->key_exists) { char filename[FILENAME_MAX]; if (prompt_keyfile(hwnd, "Save public key as:", - filename, 1, 0)) { + filename, true, false)) { int ret; FILE *fp = fopen(filename, "r"); if (fp) { @@ -1319,13 +1352,13 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, "PuTTYgen Error", MB_OK | MB_ICONERROR); } else { if (state->ssh2) { - int bloblen; - unsigned char *blob; - blob = state->ssh2key.alg->public_blob - (state->ssh2key.data, &bloblen); + strbuf *blob = strbuf_new(); + ssh_key_public_blob( + state->ssh2key.key, BinarySink_UPCAST(blob)); ssh2_write_pubkey(fp, state->ssh2key.comment, - blob, bloblen, + blob->u, blob->len, SSH_KEYTYPE_SSH2_PUBLIC_RFC4716); + strbuf_free(blob); } else { ssh1_write_pubkey(fp, &state->key); } @@ -1345,8 +1378,8 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (!state->generation_thread_exists) { char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Load private key:", - filename, 0, LOWORD(wParam)==IDC_LOAD)) { + 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); @@ -1357,24 +1390,20 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, return 0; case WM_DONEKEY: state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - state->generation_thread_exists = FALSE; - state->key_exists = TRUE; + 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.data = &state->dsskey; - state->ssh2key.alg = &ssh_dss; + state->ssh2key.key = &state->dsskey.sshk; } else if (state->keytype == ECDSA) { - state->ssh2key.data = &state->eckey; - state->ssh2key.alg = state->eckey.signalg; + state->ssh2key.key = &state->eckey.sshk; } else if (state->keytype == ED25519) { - state->ssh2key.data = &state->eckey; - state->ssh2key.alg = &ssh_ecdsa_ed25519; + state->ssh2key.key = &state->eckey.sshk; } else { - state->ssh2key.data = &state->key; - state->ssh2key.alg = &ssh_rsa; + state->ssh2key.key = &state->key.sshk; } state->commentptr = &state->ssh2key.comment; } else { @@ -1404,7 +1433,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, * Now update the key controls with all the key data. */ { - char *savecomment; + char *fp, *savecomment; /* * Blank passphrase, initially. This isn't dangerous, * because we will warn (Are You Sure?) before allowing @@ -1421,16 +1450,12 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, */ savecomment = *state->commentptr; *state->commentptr = NULL; - if (state->ssh2) { - char *fp; - fp = ssh2_fingerprint(state->ssh2key.alg, state->ssh2key.data); - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - } else { - char buf[128]; - rsa_fingerprint(buf, sizeof(buf), &state->key); - SetDlgItemText(hwnd, IDC_FINGERPRINT, buf); - } + if (state->ssh2) + fp = ssh2_fingerprint(state->ssh2key.key); + 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 @@ -1529,7 +1554,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) dll_hijacking_protection(); - InitCommonControls(); + init_common_controls(); hinst = inst; hwnd = NULL; diff --git a/windows/winpgnt.c b/windows/winpgnt.c index 70f3d2ca..bf84e863 100644 --- a/windows/winpgnt.c +++ b/windows/winpgnt.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -49,14 +50,13 @@ #define APPNAME "Pageant" -extern const char ver[]; - static HWND keylist; static HWND aboutbox; static HMENU systray_menu, session_menu; -static int already_running; +static bool already_running; static char *putty_path; +static bool restrict_putty_acl = false; /* CWD for "add key" file requester. */ static filereq *keypath = NULL; @@ -85,33 +85,10 @@ void modalfatalbox(const char *fmt, ...) exit(1); } -/* Un-munge session names out of the registry. */ -static void unmungestr(char *in, char *out, int outlen) -{ - while (*in) { - if (*in == '%' && in[1] && in[2]) { - int i, j; - - i = in[1] - '0'; - i -= (i > 9 ? 7 : 0); - j = in[2] - '0'; - j -= (j > 9 ? 7 : 0); - - *out++ = (i << 4) + j; - if (!--outlen) - return; - in += 3; - } else { - *out++ = *in++; - if (!--outlen) - return; - } - } - *out = '\0'; - return; -} +/* Stubs needed to link against misc.c */ +void queue_idempotent_callback(IdempotentCallback *ic) { assert(0); } -static int has_security; +static bool has_security; struct PassphraseProcStruct { char **passphrase; @@ -178,7 +155,7 @@ static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, case 102: /* Load web browser */ ShellExecute(hwnd, "open", - "http://www.chiark.greenend.org.uk/~sgtatham/putty/", + "https://www.chiark.greenend.org.uk/~sgtatham/putty/", 0, 0, SW_SHOWDEFAULT); return 0; } @@ -217,7 +194,7 @@ static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } SetForegroundWindow(hwnd); @@ -289,14 +266,16 @@ void keylist_update(void) if (keylist) { SendDlgItemMessage(keylist, 100, LB_RESETCONTENT, 0, 0); for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) { - char listentry[512], *p; + char *listentry, *fp, *p; + + fp = rsa_ssh1_fingerprint(rkey); + listentry = dupprintf("ssh1\t%s", fp); + sfree(fp); + /* * Replace two spaces in the fingerprint with tabs, for * nice alignment in the box. */ - strcpy(listentry, "ssh1\t"); - p = listentry + strlen(listentry); - rsa_fingerprint(p, sizeof(listentry) - (p - listentry), rkey); p = strchr(listentry, ' '); if (p) *p = '\t'; @@ -305,6 +284,7 @@ void keylist_update(void) *p = '\t'; SendDlgItemMessage(keylist, 100, LB_ADDSTRING, 0, (LPARAM) listentry); + sfree(listentry); } for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) { char *listentry, *p; @@ -335,7 +315,7 @@ void keylist_update(void) * stop and leave out a tab character. Urgh. */ - p = ssh2_fingerprint(skey->alg, skey->data); + p = ssh2_fingerprint(skey->key); listentry = dupprintf("%s\t%s", p, skey->comment); sfree(p); @@ -346,7 +326,8 @@ void keylist_update(void) break; listentry[pos++] = '\t'; } - if (skey->alg != &ssh_dss && skey->alg != &ssh_rsa) { + if (ssh_key_alg(skey->key) != &ssh_dss && + ssh_key_alg(skey->key) != &ssh_rsa) { /* * Remove the bit-count field, which is between the * first and second \t. @@ -375,34 +356,6 @@ void keylist_update(void) } } -static void answer_msg(void *msgv) -{ - unsigned char *msg = (unsigned char *)msgv; - unsigned msglen; - void *reply; - int replylen; - - msglen = GET_32BIT(msg); - if (msglen > AGENT_MAX_MSGLEN) { - reply = pageant_failure_msg(&replylen); - } else { - reply = pageant_handle_msg(msg + 4, msglen, &replylen, NULL, NULL); - if (replylen > AGENT_MAX_MSGLEN) { - smemclr(reply, replylen); - sfree(reply); - reply = pageant_failure_msg(&replylen); - } - } - - /* - * Windows Pageant answers messages in place, by overwriting the - * input message buffer. - */ - memcpy(msg, reply, replylen); - smemclr(reply, replylen); - sfree(reply); -} - static void win_add_keyfile(Filename *filename) { char *err; @@ -486,7 +439,7 @@ static void prompt_add_keyfile(void) of.lpstrFileTitle = NULL; of.lpstrTitle = "Select Private Key File"; of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; - if (request_file(keypath, &of, TRUE, FALSE)) { + if (request_file(keypath, &of, true, false)) { if(strlen(filelist) > of.nFileOffset) { /* Only one filename returned? */ Filename *fn = filename_from_str(filelist); @@ -539,7 +492,7 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, 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); + rd.right - rd.left, rd.bottom - rd.top, true); } if (has_help()) @@ -618,7 +571,7 @@ static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, if (selectedArray[itemNum] == rCount + i) { pageant_delete_ssh2_key(skey); - skey->alg->freekey(skey->data); + ssh_key_free(skey->key); sfree(skey); itemNum--; } @@ -706,6 +659,7 @@ static void update_sessions(void) HKEY hkey; TCHAR buf[MAX_PATH + 1]; MENUITEMINFO mii; + strbuf *sb; int index_key, index_menu; @@ -723,22 +677,25 @@ static void update_sessions(void) index_key = 0; index_menu = 0; + sb = strbuf_new(); while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { - TCHAR session_name[MAX_PATH + 1]; - unmungestr(buf, session_name, MAX_PATH); if(strcmp(buf, PUTTY_DEFAULT) != 0) { + sb->len = 0; + 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 = session_name; - InsertMenuItem(session_menu, index_menu, TRUE, &mii); + mii.dwTypeData = sb->s; + InsertMenuItem(session_menu, index_menu, true, &mii); index_menu++; } index_key++; } + strbuf_free(sb); RegCloseKey(hkey); @@ -748,7 +705,7 @@ static void update_sessions(void) mii.fType = MFT_STRING; mii.fState = MFS_GRAYED; mii.dwTypeData = _T("(No sessions)"); - InsertMenuItem(session_menu, index_menu, TRUE, &mii); + InsertMenuItem(session_menu, index_menu, true, &mii); } } @@ -767,7 +724,7 @@ PSID get_default_sid(void) PSECURITY_DESCRIPTOR psd = NULL; PSID sid = NULL, copy = NULL, ret = NULL; - if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE, + if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, GetCurrentProcessId())) == NULL) goto cleanup; @@ -799,10 +756,196 @@ PSID get_default_sid(void) } #endif +struct PageantReply { + char *buf; + size_t size, len; + bool overflowed; + BinarySink_IMPLEMENTATION; +}; + +static void pageant_reply_BinarySink_write( + BinarySink *bs, const void *data, size_t len) +{ + struct PageantReply *rep = BinarySink_DOWNCAST(bs, struct PageantReply); + if (!rep->overflowed && len <= rep->size - rep->len) { + memcpy(rep->buf + rep->len, data, len); + rep->len += len; + } else { + rep->overflowed = true; + } +} + +static char *answer_filemapping_message(const char *mapname) +{ + HANDLE maphandle = INVALID_HANDLE_VALUE; + void *mapaddr = NULL; + char *err = NULL; + size_t mapsize; + unsigned msglen; + struct PageantReply reply; + +#ifndef NO_SECURITY + PSID mapsid = NULL; + PSID expectedsid = NULL; + PSID expectedsid_bc = NULL; + PSECURITY_DESCRIPTOR psd = NULL; +#endif + + reply.buf = 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 + +#ifndef NO_SECURITY + 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 +#endif /* NO_SECURITY */ + { +#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 = %zd\n", mapsize)); +#endif + if (mapsize < 5) { + err = dupstr("mapping smaller than smallest possible request"); + goto cleanup; + } + + msglen = GET_32BIT((unsigned char *)mapaddr); + +#ifdef DEBUG_IPC + debug(("msg length=%08x, msg type=%02x\n", + msglen, (unsigned)((unsigned char *) mapaddr)[4])); +#endif + + reply.buf = (char *)mapaddr + 4; + reply.size = mapsize - 4; + reply.len = 0; + reply.overflowed = false; + BinarySink_INIT(&reply, pageant_reply_BinarySink_write); + + if (msglen > mapsize - 4) { + pageant_failure_msg(BinarySink_UPCAST(&reply), + "incoming length field too large", NULL, NULL); + } else { + pageant_handle_msg(BinarySink_UPCAST(&reply), + (unsigned char *)mapaddr + 4, msglen, NULL, NULL); + if (reply.overflowed) { + reply.len = 0; + reply.overflowed = false; + pageant_failure_msg(BinarySink_UPCAST(&reply), + "output would overflow message buffer", + NULL, NULL); + } + } + + if (reply.overflowed) { + err = dupstr("even failure message overflows buffer"); + goto cleanup; + } + + /* Write in the initial length field, and we're done. */ + PUT_32BIT(((unsigned char *)mapaddr), reply.len); + + 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 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { - static int menuinprogress; + static bool menuinprogress; static UINT msgTaskbarCreated = 0; switch (message) { @@ -826,32 +969,39 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, 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); + UINT menuitem = GetMenuDefaultItem(systray_menu, false, 0); if (menuitem != -1) PostMessage(hwnd, WM_COMMAND, menuitem, 0); } break; case WM_SYSTRAY2: if (!menuinprogress) { - menuinprogress = 1; + menuinprogress = true; update_sessions(); SetForegroundWindow(hwnd); TrackPopupMenu(systray_menu, TPM_RIGHTALIGN | TPM_BOTTOMALIGN | TPM_RIGHTBUTTON, wParam, lParam, 0, hwnd, NULL); - menuinprogress = 0; + menuinprogress = false; } break; case WM_COMMAND: case WM_SYSCOMMAND: switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ case IDM_PUTTY: - if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, _T(""), _T(""), - SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", - "Error", MB_OK | MB_ICONERROR); - } + { + 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 (passphrase_box) @@ -911,8 +1061,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, mii.fMask = MIIM_TYPE; mii.cch = MAX_PATH; mii.dwTypeData = buf; - GetMenuItemInfo(session_menu, wParam, FALSE, &mii); - strcpy(param, "@"); + 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) { @@ -931,14 +1084,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_COPYDATA: { COPYDATASTRUCT *cds; - char *mapname; - void *p; - HANDLE filemap; -#ifndef NO_SECURITY - PSID mapowner, ourself, ourself2; -#endif - PSECURITY_DESCRIPTOR psd = NULL; - int ret = 0; + char *mapname, *err; cds = (COPYDATASTRUCT *) lParam; if (cds->dwData != AGENT_COPYDATA_ID) @@ -946,92 +1092,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, 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(("mapname is :%s:\n", mapname)); -#endif - filemap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, mapname); -#ifdef DEBUG_IPC - debug(("filemap is %p\n", filemap)); -#endif - if (filemap != NULL && filemap != INVALID_HANDLE_VALUE) { -#ifndef NO_SECURITY - int rc; - if (has_security) { - if ((ourself = get_user_sid()) == NULL) { -#ifdef DEBUG_IPC - debug(("couldn't get user SID\n")); -#endif - CloseHandle(filemap); - return 0; - } - - if ((ourself2 = get_default_sid()) == NULL) { -#ifdef DEBUG_IPC - debug(("couldn't get default SID\n")); -#endif - CloseHandle(filemap); - return 0; - } - - if ((rc = p_GetSecurityInfo(filemap, SE_KERNEL_OBJECT, - OWNER_SECURITY_INFORMATION, - &mapowner, NULL, NULL, NULL, - &psd) != ERROR_SUCCESS)) { -#ifdef DEBUG_IPC - debug(("couldn't get owner info for filemap: %d\n", - rc)); -#endif - CloseHandle(filemap); - sfree(ourself2); - return 0; - } -#ifdef DEBUG_IPC - { - LPTSTR ours, ours2, theirs; - ConvertSidToStringSid(mapowner, &theirs); - ConvertSidToStringSid(ourself, &ours); - ConvertSidToStringSid(ourself2, &ours2); - debug(("got sids:\n oursnew=%s\n oursold=%s\n" - " theirs=%s\n", ours, ours2, theirs)); - LocalFree(ours); - LocalFree(ours2); - LocalFree(theirs); - } + debug(("IPC failed: %s\n", err)); #endif - if (!EqualSid(mapowner, ourself) && - !EqualSid(mapowner, ourself2)) { - CloseHandle(filemap); - LocalFree(psd); - sfree(ourself2); - return 0; /* security ID mismatch! */ - } -#ifdef DEBUG_IPC - debug(("security stuff matched\n")); -#endif - LocalFree(psd); - sfree(ourself2); - } else { -#ifdef DEBUG_IPC - debug(("security APIs not present\n")); -#endif - } -#endif - p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); -#ifdef DEBUG_IPC - debug(("p is %p\n", p)); - { - int i; - for (i = 0; i < 5; i++) - debug(("p[%d]=%02x\n", i, - ((unsigned char *) p)[i])); - } -#endif - answer_msg(p); - ret = 1; - UnmapViewOfFile(p); - } - CloseHandle(filemap); - return ret; + sfree(err); + return 0; + } + return 1; } } @@ -1046,8 +1115,8 @@ 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 \"%.100s\", Error: %d", cmdline, - (int)GetLastError()); + msg = dupprintf("Failed to run \"%s\": %s", cmdline, + win_strerror(GetLastError())); MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION); sfree(msg); } @@ -1076,7 +1145,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) WNDCLASS wndclass; MSG msg; const char *command = NULL; - int added_keys = 0; + bool added_keys = false; int argc, i; char **argv, **argstart; @@ -1089,14 +1158,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) * Determine whether we're an NT system (should have security * APIs) or a non-NT system (don't do security). */ - if (!init_winver()) - { - modalfatalbox("Windows refuses to report a version"); - } - if (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) { - has_security = TRUE; - } else - has_security = FALSE; + init_winver(); + has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); if (has_security) { #ifndef NO_SECURITY @@ -1169,6 +1232,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) !strcmp(argv[i], "-restrict_acl") || !strcmp(argv[i], "-restrictacl")) { restrict_process_acl(); + } else if (!strcmp(argv[i], "-restrict-putty-acl") || + !strcmp(argv[i], "-restrict_putty_acl")) { + restrict_putty_acl = true; } else if (!strcmp(argv[i], "-c")) { /* * If we see `-c', then the rest of the @@ -1184,7 +1250,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) Filename *fn = filename_from_str(argv[i]); win_add_keyfile(fn); filename_free(fn); - added_keys = TRUE; + added_keys = true; } } @@ -1266,7 +1332,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) initial_menuitems_count = GetMenuItemCount(session_menu); /* Set the default menu item. */ - SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, FALSE); + SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, false); ShowWindow(hwnd, SW_HIDE); diff --git a/windows/winpgntc.c b/windows/winpgntc.c index cd00f6a3..470fa847 100644 --- a/windows/winpgntc.c +++ b/windows/winpgntc.c @@ -15,14 +15,14 @@ #define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ -int agent_exists(void) +bool agent_exists(void) { HWND hwnd; hwnd = FindWindow("Pageant", "Pageant"); if (!hwnd) - return FALSE; + return false; else - return TRUE; + return true; } void agent_cancel_query(agent_pending_query *q) @@ -31,7 +31,7 @@ void agent_cancel_query(agent_pending_query *q) } agent_pending_query *agent_query( - void *in, int inlen, void **out, int *outlen, + strbuf *query, void **out, int *outlen, void (*callback)(void *, void *, int), void *callback_ctx) { HWND hwnd; @@ -47,6 +47,9 @@ agent_pending_query *agent_query( *out = NULL; *outlen = 0; + if (query->len > AGENT_MAX_MSGLEN) + return NULL; /* query too large */ + hwnd = FindWindow("Pageant", "Pageant"); if (!hwnd) return NULL; /* *out == NULL, so failure */ @@ -72,9 +75,9 @@ agent_pending_query *agent_query( if (psd) { if (p_InitializeSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION) && - p_SetSecurityDescriptorOwner(psd, usersid, FALSE)) { + p_SetSecurityDescriptorOwner(psd, usersid, false)) { sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; + sa.bInheritHandle = true; sa.lpSecurityDescriptor = psd; psa = &sa; } else { @@ -93,7 +96,8 @@ agent_pending_query *agent_query( return NULL; /* *out == NULL, so failure */ } p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); - memcpy(p, in, inlen); + strbuf_finalise_agent_query(query); + memcpy(p, query->s, query->len); cds.dwData = AGENT_COPYDATA_ID; cds.cbData = 1 + strlen(mapname); cds.lpData = mapname; diff --git a/windows/winplink.c b/windows/winplink.c index 47470777..fa6f4c98 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -22,84 +22,29 @@ struct agent_callback { int len; }; -void fatalbox(const char *p, ...) +void cmdline_error(const char *fmt, ...) { va_list ap; - fprintf(stderr, "FATAL ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); + va_start(ap, fmt); + console_print_error_msg_fmt_v("plink", fmt, ap); va_end(ap); - fputc('\n', stderr); - if (logctx) { - log_free(logctx); - logctx = NULL; - } - cleanup_exit(1); -} -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); - if (logctx) { - log_free(logctx); - logctx = NULL; - } - cleanup_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); -} -void connection_fatal(void *frontend, const char *p, ...) -{ - va_list ap; - fprintf(stderr, "FATAL ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - if (logctx) { - log_free(logctx); - logctx = NULL; - } - cleanup_exit(1); -} -void cmdline_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "plink: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); exit(1); } HANDLE inhandle, outhandle, errhandle; struct handle *stdin_handle, *stdout_handle, *stderr_handle; DWORD orig_console_mode; -int connopen; WSAEVENT netevent; -static Backend *back; -static void *backhandle; -static Conf *conf; +static Backend *backend; +Conf *conf; -int term_ldisc(Terminal *term, int mode) +bool term_ldisc(Terminal *term, int mode) { - return FALSE; + return false; } -void frontend_echoedit_update(void *frontend, int echo, int edit) +static void plink_echoedit_update(Seat *seat, bool echo, bool edit) { /* Update stdin read mode to reflect changes in line discipline. */ DWORD mode; @@ -116,10 +61,7 @@ void frontend_echoedit_update(void *frontend, int echo, int edit) SetConsoleMode(inhandle, mode); } -char *get_ttymode(void *frontend, const char *mode) { return NULL; } - -int from_backend(void *frontend_handle, int is_stderr, - const char *data, int len) +static int plink_output(Seat *seat, bool is_stderr, const void *data, int len) { if (is_stderr) { handle_write(stderr_handle, data, len); @@ -130,31 +72,41 @@ int from_backend(void *frontend_handle, int is_stderr, return handle_backlog(stdout_handle) + handle_backlog(stderr_handle); } -int from_backend_untrusted(void *frontend_handle, const char *data, int len) -{ - /* - * No "untrusted" output should get here (the way the code is - * currently, it's all diverted by FLAG_STDERR). - */ - assert(!"Unexpected call to from_backend_untrusted()"); - return 0; /* not reached */ -} - -int from_backend_eof(void *frontend_handle) +static bool plink_eof(Seat *seat) { handle_write_eof(stdout_handle); - return FALSE; /* do not respond to incoming EOF with outgoing */ + return false; /* do not respond to incoming EOF with outgoing */ } -int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen) +static int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) { int ret; - ret = cmdline_get_passwd_input(p, in, inlen); + ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = console_get_userpass_input(p, in, inlen); + ret = console_get_userpass_input(p); return ret; } +static const SeatVtable plink_seat_vt = { + plink_output, + plink_eof, + plink_get_userpass_input, + nullseat_notify_remote_exit, + console_connection_fatal, + nullseat_update_specials_menu, + nullseat_get_ttymode, + nullseat_set_busy_status, + console_verify_ssh_host_key, + console_confirm_weak_crypto_primitive, + console_confirm_weak_cached_hostkey, + nullseat_is_never_utf8, + plink_echoedit_update, + nullseat_get_x_display, + nullseat_get_windowid, + nullseat_get_window_pixel_size, +}; +static Seat plink_seat[1] = {{ &plink_seat_vt }}; + static DWORD main_thread_id; void agent_schedule_callback(void (*callback)(void *, void *, int), @@ -208,6 +160,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(" -noshare disable use of connection sharing\n"); + printf(" -share enable use of connection sharing\n"); printf(" -hostkey aa:bb:cc:...\n"); printf(" manually specify a host key (may be repeated)\n"); printf(" -m file read remote command(s) from file\n"); @@ -231,7 +185,7 @@ static void version(void) exit(0); } -char *do_select(SOCKET skt, int startup) +char *do_select(SOCKET skt, bool startup) { int events; if (startup) { @@ -267,11 +221,11 @@ int stdin_gotdata(struct handle *h, void *data, int len) cleanup_exit(0); } noise_ultralight(len); - if (connopen && back->connected(backhandle)) { + if (backend_connected(backend)) { if (len > 0) { - return back->send(backhandle, data, len); + return backend_send(backend, data, len); } else { - back->special(backhandle, TS_EOF); + backend_special(backend, SS_EOF, 0); return 0; } } else @@ -294,27 +248,26 @@ void stdouterr_sent(struct handle *h, int new_backlog) (h == stdout_handle ? "output" : "error"), buf); cleanup_exit(0); } - if (connopen && back->connected(backhandle)) { - back->unthrottle(backhandle, (handle_backlog(stdout_handle) + - handle_backlog(stderr_handle))); + if (backend_connected(backend)) { + backend_unthrottle(backend, (handle_backlog(stdout_handle) + + handle_backlog(stderr_handle))); } } -const int share_can_be_downstream = TRUE; -const int share_can_be_upstream = TRUE; +const bool share_can_be_downstream = true; +const bool share_can_be_upstream = true; int main(int argc, char **argv) { - int sending; - int portnumber = -1; + bool sending; SOCKET *sklist; int skcount, sksize; int exitcode; - int errors; - int got_host = FALSE; - int use_subsystem = 0; - int just_test_share_exists = FALSE; + bool errors; + bool use_subsystem = false; + bool just_test_share_exists = false; unsigned long now, next, then; + const struct BackendVtable *vt; dll_hijacking_protection(); @@ -327,26 +280,32 @@ int main(int argc, char **argv) default_protocol = PROT_SSH; default_port = 22; - flags = FLAG_STDERR; + flags = 0; + cmdline_tooltype |= + (TOOLTYPE_HOST_ARG | + TOOLTYPE_HOST_ARG_CAN_BE_SESSION | + TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | + TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD); + /* * Process the command line. */ conf = conf_new(); do_defaults(NULL, conf); - loaded_session = FALSE; + loaded_session = false; default_protocol = conf_get_int(conf, CONF_protocol); default_port = conf_get_int(conf, CONF_port); - errors = 0; + errors = false; { /* * Override the default protocol if PLINK_PROTOCOL is set. */ char *p = getenv("PLINK_PROTOCOL"); if (p) { - const Backend *b = backend_from_name(p); - if (b) { - default_protocol = b->protocol; - default_port = b->default_port; + const struct BackendVtable *vt = backend_vt_from_name(p); + if (vt) { + default_protocol = vt->protocol; + default_port = vt->default_port; conf_set_int(conf, CONF_protocol, default_protocol); conf_set_int(conf, CONF_port, default_port); } @@ -354,220 +313,72 @@ int main(int argc, char **argv) } while (--argc) { char *p = *++argv; - if (*p == '-') { - 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 = 1; - } else if (ret == 2) { - --argc, ++argv; - } else if (ret == 1) { - continue; - } else if (!strcmp(p, "-batch")) { - console_batch_mode = 1; - } else if (!strcmp(p, "-s")) { - /* Save status to write to conf later. */ - use_subsystem = 1; - } 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 { - fprintf(stderr, "plink: unknown option \"%s\"\n", p); - errors = 1; - } - } else if (*p) { - if (!conf_launchable(conf) || !(got_host || loaded_session)) { - char *q = p; - /* - * If the hostname starts with "telnet:", set the - * protocol to Telnet and process the string as a - * Telnet URL. - */ - if (!strncmp(q, "telnet:", 7)) { - char c; - - q += 7; - if (q[0] == '/' && q[1] == '/') - q += 2; - conf_set_int(conf, CONF_protocol, PROT_TELNET); - p = q; - p += host_strcspn(p, ":/"); - c = *p; - if (*p) - *p++ = '\0'; - if (c == ':') - conf_set_int(conf, CONF_port, atoi(p)); - else - conf_set_int(conf, CONF_port, -1); - conf_set_str(conf, CONF_host, q); - got_host = TRUE; - } else { - char *r, *user, *host; - /* - * Before we process the [user@]host string, we - * first check for the presence of a protocol - * prefix (a protocol name followed by ","). - */ - r = strchr(p, ','); - if (r) { - const Backend *b; - *r = '\0'; - b = backend_from_name(p); - if (b) { - default_protocol = b->protocol; - conf_set_int(conf, CONF_protocol, - default_protocol); - portnumber = b->default_port; - } - p = r + 1; - } - - /* - * A nonzero length string followed by an @ is treated - * as a username. (We discount an _initial_ @.) The - * rest of the string (or the whole string if no @) - * is treated as a session name and/or hostname. - */ - r = strrchr(p, '@'); - if (r == p) - p++, r = NULL; /* discount initial @ */ - if (r) { - *r++ = '\0'; - user = p, host = r; - } else { - user = NULL, host = p; - } - - /* - * Now attempt to load a saved session with the - * same name as the hostname. - */ - { - Conf *conf2 = conf_new(); - do_defaults(host, conf2); - if (loaded_session || !conf_launchable(conf2)) { - /* No settings for this host; use defaults */ - /* (or session was already loaded with -load) */ - conf_set_str(conf, CONF_host, host); - conf_set_int(conf, CONF_port, default_port); - got_host = TRUE; - } else { - conf_copy_into(conf, conf2); - loaded_session = TRUE; - } - conf_free(conf2); - } - - if (user) { - /* Patch in specified username. */ - conf_set_str(conf, CONF_username, user); - } - - } - } else { - char *command; - int cmdlen, cmdsize; - cmdlen = cmdsize = 0; - command = NULL; - - while (argc) { - while (*p) { - if (cmdlen >= cmdsize) { - cmdsize = cmdlen + 512; - command = sresize(command, cmdsize, char); - } - command[cmdlen++]=*p++; - } - if (cmdlen >= cmdsize) { - cmdsize = cmdlen + 512; - command = sresize(command, cmdsize, char); - } - command[cmdlen++]=' '; /* always add trailing space */ - if (--argc) p = *++argv; - } - if (cmdlen) command[--cmdlen]='\0'; - /* change trailing blank to NUL */ - conf_set_str(conf, CONF_remote_cmd, command); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_int(conf, CONF_nopty, TRUE); /* command => no tty */ - - break; /* done with cmdline */ - } - } + 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 (*p != '-') { + char *command; + int cmdlen, cmdsize; + cmdlen = cmdsize = 0; + command = NULL; + + while (argc) { + while (*p) { + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=*p++; + } + if (cmdlen >= cmdsize) { + cmdsize = cmdlen + 512; + command = sresize(command, cmdsize, char); + } + command[cmdlen++]=' '; /* always add trailing space */ + if (--argc) p = *++argv; + } + if (cmdlen) command[--cmdlen]='\0'; + /* change trailing blank to NUL */ + conf_set_str(conf, CONF_remote_cmd, command); + conf_set_str(conf, CONF_remote_cmd2, ""); + conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ + + break; /* done with cmdline */ + } else { + fprintf(stderr, "plink: unknown option \"%s\"\n", p); + errors = true; + } } if (errors) return 1; - if (!conf_launchable(conf) || !(got_host || loaded_session)) { + if (!cmdline_host_ok(conf)) { usage(); } - /* - * Muck about with the hostname in various ways. - */ - { - char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); - char *host = hostbuf; - char *p, *q; - - /* - * Trim leading whitespace. - */ - host += strspn(host, " \t"); - - /* - * See if host is of the form user@host, and separate out - * the username if so. - */ - if (host[0] != '\0') { - char *atsign = strrchr(host, '@'); - if (atsign) { - *atsign = '\0'; - conf_set_str(conf, CONF_username, host); - host = atsign + 1; - } - } - - /* - * Trim a colon suffix off the hostname if it's there. In - * order to protect unbracketed IPv6 address literals - * against this treatment, we do not do this if there's - * _more_ than one colon. - */ - { - char *c = host_strchr(host, ':'); - - if (c) { - char *d = host_strchr(c+1, ':'); - if (!d) - *c = '\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); - } + prepare_session(conf); /* * Perform command-line overrides on session configuration. @@ -578,7 +389,7 @@ int main(int argc, char **argv) * Apply subsystem status. */ if (use_subsystem) - conf_set_int(conf, CONF_ssh_subsys, TRUE); + conf_set_bool(conf, CONF_ssh_subsys, true); if (!*conf_get_str(conf, CONF_remote_cmd) && !*conf_get_str(conf, CONF_remote_cmd2) && @@ -589,19 +400,13 @@ int main(int argc, char **argv) * Select protocol. This is farmed out into a table in a * separate file to enable an ssh-free variant. */ - back = backend_from_proto(conf_get_int(conf, CONF_protocol)); - if (back == NULL) { + vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); + if (vt == NULL) { fprintf(stderr, "Internal fault: Unsupported protocol found\n"); return 1; } - /* - * Select port. - */ - if (portnumber != -1) - conf_set_int(conf, CONF_port, portnumber); - sk_init(); if (p_WSAEventSelect == NULL) { fprintf(stderr, "Plink requires WinSock 2\n"); @@ -614,55 +419,52 @@ int main(int argc, char **argv) * the "simple" flag. */ if (conf_get_int(conf, CONF_protocol) == PROT_SSH && - !conf_get_int(conf, CONF_x11_forward) && - !conf_get_int(conf, CONF_agentfwd) && + !conf_get_bool(conf, CONF_x11_forward) && + !conf_get_bool(conf, CONF_agentfwd) && !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) - conf_set_int(conf, CONF_ssh_simple, TRUE); + conf_set_bool(conf, CONF_ssh_simple, true); - logctx = log_init(NULL, conf); - console_provide_logctx(logctx); + logctx = log_init(default_logpolicy, conf); if (just_test_share_exists) { - if (!back->test_for_upstream) { + if (!vt->test_for_upstream) { fprintf(stderr, "Connection sharing not supported for connection " - "type '%s'\n", back->name); + "type '%s'\n", vt->name); return 1; } - if (back->test_for_upstream(conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), conf)) + 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) { - logevent(NULL, "Running with restricted process ACL"); + lp_eventlog(default_logpolicy, "Running with restricted process ACL"); } /* * Start up the connection. */ - netevent = CreateEvent(NULL, FALSE, FALSE, NULL); + netevent = CreateEvent(NULL, false, false, NULL); { const char *error; char *realhost; /* nodelay is only useful if stdin is a character device (console) */ - int nodelay = conf_get_int(conf, CONF_tcp_nodelay) && + bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); - error = back->init(NULL, &backhandle, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, nodelay, - conf_get_int(conf, CONF_tcp_keepalives)); + 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); return 1; } - back->provide_logctx(backhandle, logctx); sfree(realhost); } - connopen = 1; inhandle = GetStdHandle(STD_INPUT_HANDLE); outhandle = GetStdHandle(STD_OUTPUT_HANDLE); @@ -686,7 +488,7 @@ int main(int argc, char **argv) main_thread_id = GetCurrentThreadId(); - sending = FALSE; + sending = false; now = GETTICKCOUNT(); @@ -696,10 +498,10 @@ int main(int argc, char **argv) int n; DWORD ticks; - if (!sending && back->sendok(backhandle)) { + if (!sending && backend_sendok(backend)) { stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL, 0); - sending = TRUE; + sending = true; } if (toplevel_callback_pending()) { @@ -721,15 +523,13 @@ int main(int argc, char **argv) handles = handle_get_events(&nhandles); handles = sresize(handles, nhandles+1, HANDLE); handles[nhandles] = netevent; - n = MsgWaitForMultipleObjects(nhandles+1, handles, FALSE, ticks, + n = MsgWaitForMultipleObjects(nhandles+1, handles, false, ticks, QS_POSTMESSAGE); if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { handle_got_event(handles[n - WAIT_OBJECT_0]); } else if (n == WAIT_OBJECT_0 + nhandles) { WSANETWORKEVENTS things; SOCKET socket; - extern SOCKET first_socket(int *), next_socket(int *); - extern int select_result(WPARAM, LPARAM); int i, socketstate; /* @@ -782,7 +582,7 @@ int main(int argc, char **argv) LPARAM lp; int err = things.iErrorCode[eventtypes[e].bit]; lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err); - connopen &= select_result(wp, lp); + select_result(wp, lp); } } } @@ -808,13 +608,13 @@ int main(int argc, char **argv) sfree(handles); if (sending) - handle_unthrottle(stdin_handle, back->sendbuffer(backhandle)); + handle_unthrottle(stdin_handle, backend_sendbuffer(backend)); - if ((!connopen || !back->connected(backhandle)) && + if (!backend_connected(backend) && handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0) break; /* we closed the connection */ } - exitcode = back->exitcode(backhandle); + exitcode = backend_exitcode(backend); if (exitcode < 0) { fprintf(stderr, "Remote process exit code unavailable\n"); exitcode = 1; /* this is an error condition */ diff --git a/windows/winprint.c b/windows/winprint.c index c190e5fb..b8b24512 100644 --- a/windows/winprint.c +++ b/windows/winprint.c @@ -18,11 +18,51 @@ struct printer_job_tag { HANDLE hprinter; }; -static int printer_add_enum(int param, DWORD level, char **buffer, - int offset, int *nprinters_ptr) +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); /* @@ -30,21 +70,21 @@ static int printer_add_enum(int param, DWORD level, char **buffer, * we'll need for the output. Discard the return value since it * will almost certainly be a failure due to lack of space. */ - EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, - &needed, &nprinters); + p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, + &needed, &nprinters); if (needed < 512) needed = 512; *buffer = sresize(*buffer, offset+needed, char); - if (EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), - needed, &needed, &nprinters) == 0) - return FALSE; + if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), + needed, &needed, &nprinters) == 0) + return false; *nprinters_ptr += nprinters; - return TRUE; + return true; } printer_enum *printer_start_enum(int *nprinters_ptr) @@ -65,7 +105,7 @@ printer_enum *printer_start_enum(int *nprinters_ptr) * PRINTER_INFO_5 is recommended. * Bletch. */ - if (osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) { + if (osPlatformId != VER_PLATFORM_WIN32_NT) { ret->enum_level = 5; } else { ret->enum_level = 4; @@ -129,33 +169,35 @@ printer_job *printer_start_job(char *printer) { printer_job *ret = snew(printer_job); DOC_INFO_1 docinfo; - int jobstarted = 0, pagestarted = 0; + bool jobstarted = false, pagestarted = false; + + init_winfuncs(); ret->hprinter = NULL; - if (!OpenPrinter(printer, &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 (!StartDocPrinter(ret->hprinter, 1, (LPBYTE)&docinfo)) + if (!p_StartDocPrinter(ret->hprinter, 1, (LPBYTE)&docinfo)) goto error; - jobstarted = 1; + jobstarted = true; - if (!StartPagePrinter(ret->hprinter)) + if (!p_StartPagePrinter(ret->hprinter)) goto error; - pagestarted = 1; + pagestarted = true; return ret; error: if (pagestarted) - EndPagePrinter(ret->hprinter); + p_EndPagePrinter(ret->hprinter); if (jobstarted) - EndDocPrinter(ret->hprinter); + p_EndDocPrinter(ret->hprinter); if (ret->hprinter) - ClosePrinter(ret->hprinter); + p_ClosePrinter(ret->hprinter); sfree(ret); return NULL; } @@ -167,7 +209,7 @@ void printer_job_data(printer_job *pj, void *data, int len) if (!pj) return; - WritePrinter(pj->hprinter, data, len, &written); + p_WritePrinter(pj->hprinter, data, len, &written); } void printer_finish_job(printer_job *pj) @@ -175,8 +217,8 @@ void printer_finish_job(printer_job *pj) if (!pj) return; - EndPagePrinter(pj->hprinter); - EndDocPrinter(pj->hprinter); - ClosePrinter(pj->hprinter); + p_EndPagePrinter(pj->hprinter); + p_EndDocPrinter(pj->hprinter); + p_ClosePrinter(pj->hprinter); sfree(pj); } diff --git a/windows/winproxy.c b/windows/winproxy.c index ad73d9af..909af2f1 100644 --- a/windows/winproxy.c +++ b/windows/winproxy.c @@ -7,19 +7,15 @@ #include #include -#define DEFINE_PLUG_METHOD_MACROS #include "tree234.h" #include "putty.h" #include "network.h" #include "proxy.h" -Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - Plug plug, int overlapped); - -Socket platform_new_connection(SockAddr addr, const char *hostname, - int port, int privport, - int oobinline, int nodelay, int keepalive, - Plug plug, Conf *conf) +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; @@ -49,40 +45,32 @@ Socket platform_new_connection(SockAddr addr, const char *hostname, */ sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; /* default */ - sa.bInheritHandle = TRUE; + sa.bInheritHandle = true; if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { - Socket ret = - new_error_socket("Unable to create pipes for proxy command", plug); sfree(cmd); - return ret; + 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)) { - Socket ret = - new_error_socket("Unable to create pipes for proxy command", plug); sfree(cmd); CloseHandle(us_from_cmd); CloseHandle(cmd_to_us); - return ret; + return new_error_socket_fmt( + plug, "Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); } - if (flags & FLAG_STDERR) { - /* If we have a sensible stderr, the proxy command can send - * its own standard error there, so we won't interfere. */ - us_from_cmd_err = cmd_err_to_us = NULL; - } else { - /* If we don't have a sensible stderr, we should catch the - * proxy command's standard error to put in our event log. */ - if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { - Socket ret = new_error_socket - ("Unable to create pipes for proxy command", plug); - sfree(cmd); - CloseHandle(us_from_cmd); - CloseHandle(cmd_to_us); - CloseHandle(us_to_cmd); - CloseHandle(cmd_from_us); - return ret; - } + 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); @@ -100,7 +88,7 @@ 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, + CreateProcess(NULL, cmd, NULL, NULL, true, CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); CloseHandle(pi.hProcess); @@ -115,5 +103,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); + plug, false); } diff --git a/windows/winsecur.c b/windows/winsecur.c index 76dcae91..3096d26e 100644 --- a/windows/winsecur.c +++ b/windows/winsecur.c @@ -16,14 +16,14 @@ static PSID worldsid, networksid, usersid; -int got_advapi(void) +bool got_advapi(void) { - static int attempted = FALSE; - static int successful; + static bool attempted = false; + static bool successful; static HMODULE advapi; if (!attempted) { - attempted = TRUE; + attempted = true; advapi = load_system32_dll("advapi32.dll"); successful = advapi && GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) && @@ -50,7 +50,7 @@ PSID get_user_sid(void) if (!got_advapi()) goto cleanup; - if ((proc = OpenProcess(MAXIMUM_ALLOWED, FALSE, + if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, GetCurrentProcessId())) == NULL) goto cleanup; @@ -92,7 +92,7 @@ PSID get_user_sid(void) return ret; } -int getsids(char **error) +bool getsids(char **error) { #ifdef __clang__ #pragma clang diagnostic push @@ -104,7 +104,7 @@ int getsids(char **error) #pragma clang diagnostic pop #endif - int ret = FALSE; + bool ret = false; *error = NULL; @@ -135,21 +135,21 @@ int getsids(char **error) } } - ret = TRUE; + ret = true; cleanup: return ret; } -int make_private_security_descriptor(DWORD permissions, - PSECURITY_DESCRIPTOR *psd, - PACL *acl, - char **error) +bool make_private_security_descriptor(DWORD permissions, + PSECURITY_DESCRIPTOR *psd, + PACL *acl, + char **error) { EXPLICIT_ACCESS ea[3]; int acl_err; - int ret = FALSE; + bool ret = false; *psd = NULL; @@ -197,19 +197,19 @@ int make_private_security_descriptor(DWORD permissions, goto cleanup; } - if (!SetSecurityDescriptorOwner(*psd, usersid, FALSE)) { + 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)) { + if (!SetSecurityDescriptorDacl(*psd, true, *acl, false)) { *error = dupprintf("unable to set DACL in security descriptor: %s", win_strerror(GetLastError())); goto cleanup; } - ret = TRUE; + ret = true; cleanup: if (!ret) { @@ -228,11 +228,11 @@ int make_private_security_descriptor(DWORD permissions, return ret; } -static int really_restrict_process_acl(char **error) +static bool really_restrict_process_acl(char **error) { EXPLICIT_ACCESS ea[2]; int acl_err; - int ret=FALSE; + bool ret = false; PACL acl = NULL; static const DWORD nastyace=WRITE_DAC | WRITE_OWNER | @@ -279,7 +279,7 @@ static int really_restrict_process_acl(char **error) } - ret=TRUE; + ret=true; cleanup: if (!ret) { @@ -312,12 +312,12 @@ static int really_restrict_process_acl(char **error) void restrict_process_acl(void) { char *error = NULL; - int ret; + bool ret; #if !defined NO_SECURITY ret = really_restrict_process_acl(&error); #else - ret = FALSE; + ret = false; error = dupstr("ACL restrictions not compiled into this binary"); #endif if (!ret) diff --git a/windows/winsecur.h b/windows/winsecur.h index a56f7fb8..50c696db 100644 --- a/windows/winsecur.h +++ b/windows/winsecur.h @@ -33,7 +33,7 @@ DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetSecurityInfo, PSID, PSID, PACL, PACL)); DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetEntriesInAclA, (ULONG, PEXPLICIT_ACCESS, PACL, PACL *)); -int got_advapi(void); +bool got_advapi(void); /* * Find the SID describing the current user. The return value (if not @@ -46,14 +46,12 @@ PSID get_user_sid(void); * 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 + * 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 + * freed later using LocalFree). If it returns false, then instead * 'error' has been filled with a dynamically allocated error message. */ -int make_private_security_descriptor(DWORD permissions, - PSECURITY_DESCRIPTOR *psd, - PACL *acl, - char **error); +bool make_private_security_descriptor( + DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error); #endif diff --git a/windows/winser.c b/windows/winser.c index 646cd254..24c8902b 100644 --- a/windows/winser.c +++ b/windows/winser.c @@ -10,16 +10,19 @@ #define SERIAL_MAX_BACKLOG 4096 -typedef struct serial_backend_data { +typedef struct Serial Serial; +struct Serial { HANDLE port; struct handle *out, *in; - void *frontend; + Seat *seat; + LogContext *logctx; int bufsize; long clearbreak_time; - int break_in_progress; -} *Serial; + bool break_in_progress; + Backend backend; +}; -static void serial_terminate(Serial serial) +static void serial_terminate(Serial *serial) { if (serial->out) { handle_free(serial->out); @@ -39,7 +42,7 @@ static void serial_terminate(Serial serial) static int serial_gotdata(struct handle *h, void *data, int len) { - Serial serial = (Serial)handle_get_privdata(h); + Serial *serial = (Serial *)handle_get_privdata(h); if (len <= 0) { const char *error_msg; @@ -57,37 +60,37 @@ static int serial_gotdata(struct handle *h, void *data, int len) serial_terminate(serial); - notify_remote_exit(serial->frontend); + seat_notify_remote_exit(serial->seat); - logevent(serial->frontend, error_msg); + logevent(serial->logctx, error_msg); - connection_fatal(serial->frontend, "%s", error_msg); + seat_connection_fatal(serial->seat, "%s", error_msg); return 0; /* placate optimiser */ } else { - return from_backend(serial->frontend, 0, data, len); + return seat_stdout(serial->seat, data, len); } } static void serial_sentdata(struct handle *h, int new_backlog) { - Serial serial = (Serial)handle_get_privdata(h); + Serial *serial = (Serial *)handle_get_privdata(h); if (new_backlog < 0) { const char *error_msg = "Error writing to serial device"; serial_terminate(serial); - notify_remote_exit(serial->frontend); + seat_notify_remote_exit(serial->seat); - logevent(serial->frontend, error_msg); + logevent(serial->logctx, error_msg); - connection_fatal(serial->frontend, "%s", error_msg); + seat_connection_fatal(serial->seat, "%s", error_msg); } else { serial->bufsize = new_backlog; } } -static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf) +static const char *serial_configure(Serial *serial, HANDLE serport, Conf *conf) { DCB dcb; COMMTIMEOUTS timeouts; @@ -99,37 +102,32 @@ static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf) * device instead of a serial port. */ if (GetCommState(serport, &dcb)) { - char *msg; const char *str; /* * Boilerplate. */ - dcb.fBinary = TRUE; + 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.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; + dcb.fAbortOnError = false; + dcb.fOutxCtsFlow = false; + dcb.fOutxDsrFlow = false; /* * Configurable parameters. */ dcb.BaudRate = conf_get_int(conf, CONF_serspeed); - msg = dupprintf("Configuring baud rate %lu", dcb.BaudRate); - logevent(serial->frontend, msg); - sfree(msg); + logeventf(serial->logctx, "Configuring baud rate %lu", dcb.BaudRate); dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); - msg = dupprintf("Configuring %u data bits", dcb.ByteSize); - logevent(serial->frontend, msg); - sfree(msg); + logeventf(serial->logctx, "Configuring %u data bits", dcb.ByteSize); switch (conf_get_int(conf, CONF_serstopbits)) { case 2: dcb.StopBits = ONESTOPBIT; str = "1"; break; @@ -137,9 +135,7 @@ static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf) case 4: dcb.StopBits = TWOSTOPBITS; str = "2"; break; default: return "Invalid number of stop bits (need 1, 1.5 or 2)"; } - msg = dupprintf("Configuring %s data bits", str); - logevent(serial->frontend, msg); - sfree(msg); + logeventf(serial->logctx, "Configuring %s data bits", str); switch (conf_get_int(conf, CONF_serparity)) { case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; @@ -148,32 +144,28 @@ static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf) case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; } - msg = dupprintf("Configuring %s parity", str); - logevent(serial->frontend, msg); - sfree(msg); + 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; + dcb.fOutX = dcb.fInX = true; str = "XON/XOFF"; break; case SER_FLOW_RTSCTS: dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; - dcb.fOutxCtsFlow = TRUE; + dcb.fOutxCtsFlow = true; str = "RTS/CTS"; break; case SER_FLOW_DSRDTR: dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; - dcb.fOutxDsrFlow = TRUE; + dcb.fOutxDsrFlow = true; str = "DSR/DTR"; break; } - msg = dupprintf("Configuring %s flow control", str); - logevent(serial->frontend, msg); - sfree(msg); + logeventf(serial->logctx, "Configuring %s flow control", str); if (!SetCommState(serport, &dcb)) return "Unable to configure serial port"; @@ -198,30 +190,29 @@ static const char *serial_configure(Serial serial, HANDLE serport, Conf *conf) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *serial_init(void *frontend_handle, void **backend_handle, - Conf *conf, const char *host, int port, - char **realhost, int nodelay, int keepalive) +static const char *serial_init(Seat *seat, Backend **backend_handle, + LogContext *logctx, Conf *conf, + const char *host, int port, + char **realhost, bool nodelay, bool keepalive) { - Serial serial; + Serial *serial; HANDLE serport; const char *err; char *serline; - serial = snew(struct serial_backend_data); + serial = snew(Serial); serial->port = INVALID_HANDLE_VALUE; serial->out = serial->in = NULL; serial->bufsize = 0; - serial->break_in_progress = FALSE; - *backend_handle = serial; + serial->break_in_progress = false; + serial->backend.vt = &serial_backend; + *backend_handle = &serial->backend; - serial->frontend = frontend_handle; + serial->seat = seat; + serial->logctx = logctx; serline = conf_get_str(conf, CONF_serline); - { - char *msg = dupprintf("Opening serial device %s", serline); - logevent(serial->frontend, msg); - sfree(msg); - } + logeventf(serial->logctx, "Opening serial device %s", serline); { /* @@ -274,38 +265,38 @@ static const char *serial_init(void *frontend_handle, void **backend_handle, /* * Specials are always available. */ - update_specials_menu(serial->frontend); + seat_update_specials_menu(serial->seat); return NULL; } -static void serial_free(void *handle) +static void serial_free(Backend *be) { - Serial serial = (Serial) handle; + Serial *serial = container_of(be, Serial, backend); serial_terminate(serial); expire_timer_context(serial); sfree(serial); } -static void serial_reconfig(void *handle, Conf *conf) +static void serial_reconfig(Backend *be, Conf *conf) { - Serial serial = (Serial) handle; - const char *err; + Serial *serial = container_of(be, Serial, backend); - err = serial_configure(serial, serial->port, conf); + serial_configure(serial, serial->port, conf); /* - * FIXME: what should we do if err returns something? + * FIXME: what should we do if that call returned a non-NULL error + * message? */ } /* * Called to send data down the serial connection. */ -static int serial_send(void *handle, const char *buf, int len) +static int serial_send(Backend *be, const char *buf, int len) { - Serial serial = (Serial) handle; + Serial *serial = container_of(be, Serial, backend); if (serial->out == NULL) return 0; @@ -317,16 +308,16 @@ static int serial_send(void *handle, const char *buf, int len) /* * Called to query the current sendability status. */ -static int serial_sendbuffer(void *handle) +static int serial_sendbuffer(Backend *be) { - Serial serial = (Serial) handle; + Serial *serial = container_of(be, Serial, backend); return serial->bufsize; } /* * Called to set the size of the window */ -static void serial_size(void *handle, int width, int height) +static void serial_size(Backend *be, int width, int height) { /* Do nothing! */ return; @@ -334,24 +325,24 @@ static void serial_size(void *handle, int width, int height) static void serbreak_timer(void *ctx, unsigned long now) { - Serial serial = (Serial)ctx; + Serial *serial = (Serial *)ctx; if (now == serial->clearbreak_time && serial->port) { ClearCommBreak(serial->port); - serial->break_in_progress = FALSE; - logevent(serial->frontend, "Finished serial break"); + serial->break_in_progress = false; + logevent(serial->logctx, "Finished serial break"); } } /* * Send serial special codes. */ -static void serial_special(void *handle, Telnet_Special code) +static void serial_special(Backend *be, SessionSpecialCode code, int arg) { - Serial serial = (Serial) handle; + Serial *serial = container_of(be, Serial, backend); - if (serial->port && code == TS_BRK) { - logevent(serial->frontend, "Starting serial break at user request"); + 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 @@ -365,7 +356,7 @@ static void serial_special(void *handle, Telnet_Special code) */ serial->clearbreak_time = schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial); - serial->break_in_progress = TRUE; + serial->break_in_progress = true; } return; @@ -375,53 +366,48 @@ static void serial_special(void *handle, Telnet_Special code) * Return a list of the special codes that make sense in this * protocol. */ -static const struct telnet_special *serial_get_specials(void *handle) +static const SessionSpecial *serial_get_specials(Backend *be) { - static const struct telnet_special specials[] = { - {"Break", TS_BRK}, - {NULL, TS_EXITMENU} + static const SessionSpecial specials[] = { + {"Break", SS_BRK}, + {NULL, SS_EXITMENU} }; return specials; } -static int serial_connected(void *handle) +static bool serial_connected(Backend *be) { - return 1; /* always connected */ + return true; /* always connected */ } -static int serial_sendok(void *handle) +static bool serial_sendok(Backend *be) { - return 1; + return true; } -static void serial_unthrottle(void *handle, int backlog) +static void serial_unthrottle(Backend *be, int backlog) { - Serial serial = (Serial) handle; + Serial *serial = container_of(be, Serial, backend); if (serial->in) handle_unthrottle(serial->in, backlog); } -static int serial_ldisc(void *handle, int option) +static bool serial_ldisc(Backend *be, int option) { /* * Local editing and local echo are off by default. */ - return 0; -} - -static void serial_provide_ldisc(void *handle, void *ldisc) -{ - /* This is a stub. */ + return false; } -static void serial_provide_logctx(void *handle, void *logctx) +static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) { /* This is a stub. */ } -static int serial_exitcode(void *handle) +static int serial_exitcode(Backend *be) { - Serial serial = (Serial) handle; + Serial *serial = container_of(be, Serial, backend); if (serial->port != INVALID_HANDLE_VALUE) return -1; /* still connected */ else @@ -432,12 +418,12 @@ static int serial_exitcode(void *handle) /* * cfg_info for Serial does nothing at all. */ -static int serial_cfg_info(void *handle) +static int serial_cfg_info(Backend *be) { return 0; } -Backend serial_backend = { +const struct BackendVtable serial_backend = { serial_init, serial_free, serial_reconfig, @@ -451,7 +437,6 @@ Backend serial_backend = { serial_sendok, serial_ldisc, serial_provide_ldisc, - serial_provide_logctx, serial_unthrottle, serial_cfg_info, NULL /* test_for_upstream */, diff --git a/windows/winsftp.c b/windows/winsftp.c index e25d7e06..f9f6222c 100644 --- a/windows/winsftp.c +++ b/windows/winsftp.c @@ -10,17 +10,14 @@ #include "putty.h" #include "psftp.h" #include "ssh.h" -#include "int64.h" #include "winsecur.h" -char *get_ttymode(void *frontend, const char *mode) { return NULL; } - -int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen) +int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) { int ret; - ret = cmdline_get_passwd_input(p, in, inlen); + ret = cmdline_get_passwd_input(p); if (ret == -1) - ret = console_get_userpass_input(p, in, inlen); + ret = console_get_userpass_input(p); return ret; } @@ -28,7 +25,7 @@ void platform_get_x11_auth(struct X11Display *display, Conf *conf) { /* Do nothing, therefore no auth. */ } -const int platform_uses_x11_unix_by_default = TRUE; +const bool platform_uses_x11_unix_by_default = true; /* ---------------------------------------------------------------------- * File access abstraction. @@ -73,6 +70,11 @@ char *psftp_getcwd(void) 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; \ @@ -91,7 +93,7 @@ struct RFile { HANDLE h; }; -RFile *open_existing_file(const char *name, uint64 *size, +RFile *open_existing_file(const char *name, uint64_t *size, unsigned long *mtime, unsigned long *atime, long *perms) { @@ -109,8 +111,7 @@ RFile *open_existing_file(const char *name, uint64 *size, if (size) { DWORD lo, hi; lo = GetFileSize(h, &hi); - size->lo = lo; - size->hi = hi; + *size = uint64_from_words(hi, lo); } if (mtime || atime) { @@ -130,10 +131,8 @@ RFile *open_existing_file(const char *name, uint64 *size, int read_from_file(RFile *f, void *buffer, int length) { - int ret; DWORD read; - ret = ReadFile(f->h, buffer, length, &read, NULL); - if (!ret) + if (!ReadFile(f->h, buffer, length, &read, NULL)) return -1; /* error */ else return read; @@ -165,7 +164,7 @@ WFile *open_new_file(const char *name, long perms) return ret; } -WFile *open_existing_wfile(const char *name, uint64 *size) +WFile *open_existing_wfile(const char *name, uint64_t *size) { HANDLE h; WFile *ret; @@ -181,8 +180,7 @@ WFile *open_existing_wfile(const char *name, uint64 *size) if (size) { DWORD lo, hi; lo = GetFileSize(h, &hi); - size->lo = lo; - size->hi = hi; + *size = uint64_from_words(hi, lo); } return ret; @@ -190,10 +188,8 @@ WFile *open_existing_wfile(const char *name, uint64 *size) int write_to_file(WFile *f, void *buffer, int length) { - int ret; DWORD written; - ret = WriteFile(f->h, buffer, length, &written, NULL); - if (!ret) + if (!WriteFile(f->h, buffer, length, &written, NULL)) return -1; /* error */ else return written; @@ -215,7 +211,7 @@ void close_wfile(WFile *f) /* Seek offset bytes through file, from whence, where whence is FROM_START, FROM_CURRENT, or FROM_END */ -int seek_file(WFile *f, uint64 offset, int whence) +int seek_file(WFile *f, uint64_t offset, int whence) { DWORD movemethod; @@ -234,7 +230,7 @@ int seek_file(WFile *f, uint64 offset, int whence) } { - LONG lo = offset.lo, hi = offset.hi; + LONG lo = offset & 0xFFFFFFFFU, hi = offset >> 32; SetFilePointer(f->h, lo, &hi, movemethod); } @@ -244,16 +240,12 @@ int seek_file(WFile *f, uint64 offset, int whence) return 0; } -uint64 get_file_posn(WFile *f) +uint64_t get_file_posn(WFile *f) { - uint64 ret; LONG lo, hi = 0; lo = SetFilePointer(f->h, 0L, &hi, FILE_CURRENT); - ret.lo = lo; - ret.hi = hi; - - return ret; + return uint64_from_words(hi, lo); } int file_type(const char *name) @@ -300,8 +292,7 @@ char *read_filename(DirHandle *dir) if (!dir->name) { WIN32_FIND_DATA fdat; - int ok = FindNextFile(dir->h, &fdat); - if (!ok) + if (!FindNextFile(dir->h, &fdat)) return NULL; else dir->name = dupstr(fdat.cFileName); @@ -333,7 +324,7 @@ void close_directory(DirHandle *dir) sfree(dir); } -int test_wildcard(const char *name, int cmdline) +int test_wildcard(const char *name, bool cmdline) { HANDLE fh; WIN32_FIND_DATA fdat; @@ -357,7 +348,7 @@ struct WildcardMatcher { char *srcpath; }; -char *stripslashes(const char *str, int local) +char *stripslashes(const char *str, bool local) { char *p; @@ -395,7 +386,7 @@ WildcardMatcher *begin_wildcard_matching(const char *name) ret = snew(WildcardMatcher); ret->h = h; ret->srcpath = dupstr(name); - last = stripslashes(ret->srcpath, 1); + last = stripslashes(ret->srcpath, true); *last = '\0'; if (fdat.cFileName[0] == '.' && (fdat.cFileName[1] == '\0' || @@ -411,9 +402,8 @@ char *wildcard_get_filename(WildcardMatcher *dir) { while (!dir->name) { WIN32_FIND_DATA fdat; - int ok = FindNextFile(dir->h, &fdat); - if (!ok) + if (!FindNextFile(dir->h, &fdat)) return NULL; if (fdat.cFileName[0] == '.' && @@ -441,18 +431,18 @@ void finish_wildcard_matching(WildcardMatcher *dir) sfree(dir); } -int vet_filename(const char *name) +bool vet_filename(const char *name) { if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) - return FALSE; + return false; if (!name[strspn(name, ".")]) /* entirely composed of dots */ - return FALSE; + return false; - return TRUE; + return true; } -int create_directory(const char *name) +bool create_directory(const char *name) { return CreateDirectory(name, NULL) != 0; } @@ -471,7 +461,7 @@ char *dir_file_cat(const char *dir, const char *file) */ static SOCKET sftp_ssh_socket = INVALID_SOCKET; static HANDLE netevent = INVALID_HANDLE_VALUE; -char *do_select(SOCKET skt, int startup) +char *do_select(SOCKET skt, bool startup) { int events; if (startup) @@ -483,7 +473,7 @@ char *do_select(SOCKET skt, int startup) if (startup) { events = (FD_CONNECT | FD_READ | FD_WRITE | FD_OOB | FD_CLOSE | FD_ACCEPT); - netevent = CreateEvent(NULL, FALSE, FALSE, NULL); + netevent = CreateEvent(NULL, false, false, NULL); } else { events = 0; } @@ -498,7 +488,6 @@ char *do_select(SOCKET skt, int startup) } return NULL; } -extern int select_result(WPARAM, LPARAM); int do_eventsel_loop(HANDLE other_event) { @@ -539,15 +528,13 @@ int do_eventsel_loop(HANDLE other_event) else otherindex = -1; - n = WaitForMultipleObjects(nallhandles, handles, FALSE, ticks); + n = WaitForMultipleObjects(nallhandles, handles, false, ticks); if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { handle_got_event(handles[n - WAIT_OBJECT_0]); } else if (netindex >= 0 && n == WAIT_OBJECT_0 + netindex) { WSANETWORKEVENTS things; SOCKET socket; - extern SOCKET first_socket(int *), next_socket(int *); - extern int select_result(WPARAM, LPARAM); int i, socketstate; /* @@ -708,7 +695,7 @@ static DWORD WINAPI command_read_thread(void *param) return 0; } -char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok) +char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok) { int ret; struct command_read_ctx actx, *ctx = &actx; @@ -727,7 +714,7 @@ char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok) * Create a second thread to read from stdin. Process network * and timing events until it terminates. */ - ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL); + ctx->event = CreateEvent(NULL, false, false, NULL); ctx->line = NULL; hThread = CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid); @@ -753,7 +740,7 @@ char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok) void platform_psftp_pre_conn_setup(void) { if (restricted_acl) { - logevent(NULL, "Running with restricted process ACL"); + lp_eventlog(default_logpolicy, "Running with restricted process ACL"); } } diff --git a/windows/winshare.c b/windows/winshare.c index 56276045..6498cc4e 100644 --- a/windows/winshare.c +++ b/windows/winshare.c @@ -7,7 +7,6 @@ #if !defined NO_SECURITY -#define DEFINE_PLUG_METHOD_MACROS #include "tree234.h" #include "putty.h" #include "network.h" @@ -49,7 +48,6 @@ static char *obfuscate_name(const char *realname) char *cryptdata; int cryptlen; SHA256_State sha; - unsigned char lenbuf[4]; unsigned char digest[32]; char retbuf[65]; int i; @@ -90,9 +88,7 @@ static char *obfuscate_name(const char *realname) * so having got it back out of CryptProtectMemory we now hash it. */ SHA256_Init(&sha); - PUT_32BIT_MSB_FIRST(lenbuf, cryptlen); - SHA256_Bytes(&sha, lenbuf, 4); - SHA256_Bytes(&sha, cryptdata, cryptlen); + put_string(&sha, cryptdata, cryptlen); SHA256_Final(&sha, digest); sfree(cryptdata); @@ -119,17 +115,14 @@ static char *make_name(const char *prefix, const char *name) return retname; } -Socket new_named_pipe_client(const char *pipename, Plug plug); -Socket new_named_pipe_listener(const char *pipename, Plug plug); - int platform_ssh_share(const char *pi_name, Conf *conf, - Plug downplug, Plug upplug, Socket *sock, + Plug *downplug, Plug *upplug, Socket **sock, char **logtext, char **ds_err, char **us_err, - int can_upstream, int can_downstream) + bool can_upstream, bool can_downstream) { char *name, *mutexname, *pipename; HANDLE mutex; - Socket retsock; + Socket *retsock; PSECURITY_DESCRIPTOR psd; PACL acl; @@ -165,9 +158,9 @@ int platform_ssh_share(const char *pi_name, Conf *conf, memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = FALSE; + sa.bInheritHandle = false; - mutex = CreateMutex(&sa, FALSE, mutexname); + mutex = CreateMutex(&sa, false, mutexname); if (!mutex) { *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", diff --git a/windows/winstore.c b/windows/winstore.c index 26cbf634..3b80e147 100644 --- a/windows/winstore.c +++ b/windows/winstore.c @@ -22,136 +22,103 @@ static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist"; static const char *const reg_jumplist_value = "Recent sessions"; static const char *const puttystr = PUTTY_REG_POS "\\Sessions"; -static const char hex[16] = "0123456789ABCDEF"; - -static int tried_shgetfolderpath = FALSE; +static bool tried_shgetfolderpath = false; static HMODULE shell32_module = NULL; DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, (HWND, int, HANDLE, DWORD, LPSTR)); -static void mungestr(const char *in, char *out) -{ - int candot = 0; - - while (*in) { - if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || - *in == '%' || *in < ' ' || *in > '~' || (*in == '.' - && !candot)) { - *out++ = '%'; - *out++ = hex[((unsigned char) *in) >> 4]; - *out++ = hex[((unsigned char) *in) & 15]; - } else - *out++ = *in; - in++; - candot = 1; - } - *out = '\0'; - return; -} - -static void unmungestr(const char *in, char *out, int outlen) -{ - while (*in) { - if (*in == '%' && in[1] && in[2]) { - int i, j; - - i = in[1] - '0'; - i -= (i > 9 ? 7 : 0); - j = in[2] - '0'; - j -= (j > 9 ? 7 : 0); - - *out++ = (i << 4) + j; - if (!--outlen) - return; - in += 3; - } else { - *out++ = *in++; - if (!--outlen) - return; - } - } - *out = '\0'; - return; -} +struct settings_w { + HKEY sesskey; +}; -void *open_settings_w(const char *sessionname, char **errmsg) +settings_w *open_settings_w(const char *sessionname, char **errmsg) { HKEY subkey1, sesskey; int ret; - char *p; + strbuf *sb; *errmsg = NULL; if (!sessionname || !*sessionname) sessionname = "Default Settings"; - p = snewn(3 * strlen(sessionname) + 1, char); - mungestr(sessionname, p); + sb = strbuf_new(); + escape_registry_key(sessionname, sb); ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); if (ret != ERROR_SUCCESS) { - sfree(p); + strbuf_free(sb); *errmsg = dupprintf("Unable to create registry key\n" "HKEY_CURRENT_USER\\%s", puttystr); return NULL; } - ret = RegCreateKey(subkey1, p, &sesskey); + 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, p); - sfree(p); + "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); + strbuf_free(sb); return NULL; } - sfree(p); - return (void *) sesskey; + strbuf_free(sb); + + settings_w *toret = snew(settings_w); + toret->sesskey = sesskey; + return toret; } -void write_setting_s(void *handle, const char *key, const char *value) +void write_setting_s(settings_w *handle, const char *key, const char *value) { if (handle) - RegSetValueEx((HKEY) handle, key, 0, REG_SZ, (CONST BYTE *)value, + RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value, 1 + strlen(value)); } -void write_setting_i(void *handle, const char *key, int value) +void write_setting_i(settings_w *handle, const char *key, int value) { if (handle) - RegSetValueEx((HKEY) handle, key, 0, REG_DWORD, + RegSetValueEx(handle->sesskey, key, 0, REG_DWORD, (CONST BYTE *) &value, sizeof(value)); } -void close_settings_w(void *handle) +void close_settings_w(settings_w *handle) { - RegCloseKey((HKEY) handle); + RegCloseKey(handle->sesskey); + sfree(handle); } -void *open_settings_r(const char *sessionname) +struct settings_r { + HKEY sesskey; +}; + +settings_r *open_settings_r(const char *sessionname) { HKEY subkey1, sesskey; - char *p; + strbuf *sb; if (!sessionname || !*sessionname) sessionname = "Default Settings"; - p = snewn(3 * strlen(sessionname) + 1, char); - mungestr(sessionname, p); + sb = strbuf_new(); + escape_registry_key(sessionname, sb); if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { sesskey = NULL; } else { - if (RegOpenKey(subkey1, p, &sesskey) != ERROR_SUCCESS) { + if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) { sesskey = NULL; } RegCloseKey(subkey1); } - sfree(p); + strbuf_free(sb); - return (void *) sesskey; + settings_r *toret = snew(settings_r); + toret->sesskey = sesskey; + return toret; } -char *read_setting_s(void *handle, const char *key) +char *read_setting_s(settings_r *handle, const char *key) { DWORD type, allocsize, size; char *ret; @@ -160,14 +127,14 @@ char *read_setting_s(void *handle, const char *key) return NULL; /* Find out the type and size of the data. */ - if (RegQueryValueEx((HKEY) handle, key, 0, + 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((HKEY) handle, key, 0, + if (RegQueryValueEx(handle->sesskey, key, 0, &type, (BYTE *)ret, &size) != ERROR_SUCCESS || type != REG_SZ) { sfree(ret); @@ -180,13 +147,13 @@ char *read_setting_s(void *handle, const char *key) return ret; } -int read_setting_i(void *handle, const char *key, int defvalue) +int read_setting_i(settings_r *handle, const char *key, int defvalue) { DWORD type, val, size; size = sizeof(val); if (!handle || - RegQueryValueEx((HKEY) handle, key, 0, &type, + RegQueryValueEx(handle->sesskey, key, 0, &type, (BYTE *) &val, &size) != ERROR_SUCCESS || size != sizeof(val) || type != REG_DWORD) return defvalue; @@ -194,7 +161,7 @@ int read_setting_i(void *handle, const char *key, int defvalue) return val; } -FontSpec *read_setting_fontspec(void *handle, const char *name) +FontSpec *read_setting_fontspec(settings_r *handle, const char *name) { char *settingname; char *fontname; @@ -234,7 +201,8 @@ FontSpec *read_setting_fontspec(void *handle, const char *name) return ret; } -void write_setting_fontspec(void *handle, const char *name, FontSpec *font) +void write_setting_fontspec(settings_w *handle, + const char *name, FontSpec *font) { char *settingname; @@ -250,7 +218,7 @@ void write_setting_fontspec(void *handle, const char *name, FontSpec *font) sfree(settingname); } -Filename *read_setting_filename(void *handle, const char *name) +Filename *read_setting_filename(settings_r *handle, const char *name) { char *tmp = read_setting_s(handle, name); if (tmp) { @@ -261,48 +229,50 @@ Filename *read_setting_filename(void *handle, const char *name) return NULL; } -void write_setting_filename(void *handle, const char *name, Filename *result) +void write_setting_filename(settings_w *handle, + const char *name, Filename *result) { write_setting_s(handle, name, result->path); } -void close_settings_r(void *handle) +void close_settings_r(settings_r *handle) { - RegCloseKey((HKEY) handle); + RegCloseKey(handle->sesskey); + sfree(handle); } void del_settings(const char *sessionname) { HKEY subkey1; - char *p; + strbuf *sb; if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) return; - p = snewn(3 * strlen(sessionname) + 1, char); - mungestr(sessionname, p); - RegDeleteKey(subkey1, p); - sfree(p); + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + RegDeleteKey(subkey1, sb->s); + strbuf_free(sb); RegCloseKey(subkey1); remove_session_from_jumplist(sessionname); } -struct enumsettings { +struct settings_e { HKEY key; int i; }; -void *enum_settings_start(void) +settings_e *enum_settings_start(void) { - struct enumsettings *ret; + settings_e *ret; HKEY key; if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) return NULL; - ret = snew(struct enumsettings); + ret = snew(settings_e); if (ret) { ret->key = key; ret->i = 0; @@ -311,43 +281,47 @@ void *enum_settings_start(void) return ret; } -char *enum_settings_next(void *handle, char *buffer, int buflen) +bool enum_settings_next(settings_e *e, strbuf *sb) { - struct enumsettings *e = (struct enumsettings *) handle; - char *otherbuf; - otherbuf = snewn(3 * buflen, char); - if (RegEnumKey(e->key, e->i++, otherbuf, 3 * buflen) == ERROR_SUCCESS) { - unmungestr(otherbuf, buffer, buflen); - sfree(otherbuf); - return buffer; - } else { - sfree(otherbuf); - return NULL; + size_t regbuf_size = 256; + 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; + } + regbuf_size = regbuf_size * 5 / 4 + 256; + regbuf = sresize(regbuf, regbuf_size, char); } + + if (success) + unescape_registry_key(regbuf, sb); + + sfree(regbuf); + return success; } -void enum_settings_finish(void *handle) +void enum_settings_finish(settings_e *e) { - struct enumsettings *e = (struct enumsettings *) handle; RegCloseKey(e->key); sfree(e); } -static void hostkey_regname(char *buffer, const char *hostname, +static void hostkey_regname(strbuf *sb, const char *hostname, int port, const char *keytype) { - int len; - strcpy(buffer, keytype); - strcat(buffer, "@"); - len = strlen(buffer); - len += sprintf(buffer + len, "%d:", port); - mungestr(hostname, buffer + strlen(buffer)); + 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, *regname; + char *otherstr; + strbuf *regname; int len; HKEY rkey; DWORD readlen; @@ -360,19 +334,18 @@ int verify_host_key(const char *hostname, int port, * Now read a saved key in from the registry and see what it * says. */ - regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); - + regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", &rkey) != ERROR_SUCCESS) { - sfree(regname); + strbuf_free(regname); return 1; /* key does not exist in registry */ } readlen = len; otherstr = snewn(len, char); - ret = RegQueryValueEx(rkey, regname, NULL, + ret = RegQueryValueEx(rkey, regname->s, NULL, &type, (BYTE *)otherstr, &readlen); if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && @@ -382,7 +355,7 @@ int verify_host_key(const char *hostname, int port, * another trick, which is to look up the _old_ key format * under just the hostname and translate that. */ - char *justhost = regname + 1 + strcspn(regname, ":"); + char *justhost = regname->s + 1 + strcspn(regname->s, ":"); char *oldstyle = snewn(len + 10, char); /* safety margin */ readlen = len; ret = RegQueryValueEx(rkey, justhost, NULL, &type, @@ -432,7 +405,7 @@ int verify_host_key(const char *hostname, int port, * wrong, and hyper-cautiously do nothing. */ if (!strcmp(otherstr, key)) - RegSetValueEx(rkey, regname, 0, REG_SZ, (BYTE *)otherstr, + RegSetValueEx(rkey, regname->s, 0, REG_SZ, (BYTE *)otherstr, strlen(otherstr) + 1); } @@ -444,7 +417,7 @@ int verify_host_key(const char *hostname, int port, compare = strcmp(otherstr, key); sfree(otherstr); - sfree(regname); + strbuf_free(regname); if (ret == ERROR_MORE_DATA || (ret == ERROR_SUCCESS && type == REG_SZ && compare)) @@ -455,7 +428,7 @@ int verify_host_key(const char *hostname, int port, return 0; /* key matched OK in registry */ } -int have_ssh_host_key(const char *hostname, int port, +bool have_ssh_host_key(const char *hostname, int port, const char *keytype) { /* @@ -468,16 +441,16 @@ int have_ssh_host_key(const char *hostname, int port, void store_host_key(const char *hostname, int port, const char *keytype, const char *key) { - char *regname; + strbuf *regname; HKEY rkey; - regname = snewn(3 * (strlen(hostname) + strlen(keytype)) + 15, char); - + regname = strbuf_new(); hostkey_regname(regname, hostname, port, keytype); if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", &rkey) == ERROR_SUCCESS) { - RegSetValueEx(rkey, regname, 0, REG_SZ, (BYTE *)key, strlen(key) + 1); + RegSetValueEx(rkey, regname->s, 0, REG_SZ, + (BYTE *)key, strlen(key) + 1); RegCloseKey(rkey); } /* else key does not exist in registry */ @@ -488,7 +461,7 @@ void store_host_key(const char *hostname, int port, * Open (or delete) the random seed file. */ enum { DEL, OPEN_R, OPEN_W }; -static int try_random_seed(char const *path, int action, HANDLE *ret) +static bool try_random_seed(char const *path, int action, HANDLE *ret) { if (action == DEL) { if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) { @@ -496,7 +469,7 @@ static int try_random_seed(char const *path, int action, HANDLE *ret) win_strerror(GetLastError())); } *ret = INVALID_HANDLE_VALUE; - return FALSE; /* so we'll do the next ones too */ + return false; /* so we'll do the next ones too */ } *ret = CreateFile(path, @@ -561,7 +534,7 @@ static HANDLE access_random_seed(int action) * so stuff that. */ shell32_module = load_system32_dll("shell32.dll"); GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); - tried_shgetfolderpath = TRUE; + tried_shgetfolderpath = true; } if (p_SHGetFolderPathA) { if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, @@ -656,7 +629,7 @@ static int transform_jumplist_registry (const char *add, const char *rem, char **out) { int ret; - HKEY pjumplist_key, psettings_tmp; + HKEY pjumplist_key; DWORD type; DWORD value_length; char *old_value, *new_value; @@ -741,7 +714,7 @@ static int transform_jumplist_registry while (*piterator_old != '\0') { if (!rem || strcmp(piterator_old, rem) != 0) { /* Check if this is a valid session, otherwise don't add. */ - psettings_tmp = open_settings_r(piterator_old); + settings_r *psettings_tmp = open_settings_r(piterator_old); if (psettings_tmp != NULL) { close_settings_r(psettings_tmp); strcpy(piterator_new, piterator_old); diff --git a/windows/winstuff.h b/windows/winstuff.h index c1918d4a..cd128156 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -19,15 +19,23 @@ * 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 +#if !defined _MSC_VER || _MSC_VER >= 1600 || defined __clang__ #include #endif +#include "defs.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; @@ -36,12 +44,12 @@ struct Filename { struct FontSpec { char *name; - int isbold; + bool isbold; int height; int charset; }; -struct FontSpec *fontspec_new(const char *name, - int bold, int height, int charset); +struct FontSpec *fontspec_new( + const char *name, bool bold, int height, int charset); #ifndef CLEARTYPE_QUALITY #define CLEARTYPE_QUALITY 5 @@ -55,6 +63,10 @@ struct FontSpec *fontspec_new(const char *name, #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 @@ -128,15 +140,22 @@ struct FontSpec *fontspec_new(const char *name, * * (DECL_WINDOWS_FUNCTION works with both these variants.) */ -#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \ - typedef rettype (WINAPI *t_##name) params; \ +#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \ + typedef rettype (WINAPI *t_##name) params; \ linkage t_##name p_##name #define STR1(x) #x #define STR(x) STR1(x) -#define GET_WINDOWS_FUNCTION_PP(module, name) \ - (p_##name = module ? (t_##name) GetProcAddress(module, STR(name)) : NULL) -#define GET_WINDOWS_FUNCTION(module, name) \ - (p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL) +#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) /* * Global variables. Most modules declare these `extern', but @@ -151,13 +170,6 @@ struct FontSpec *fontspec_new(const char *name, #endif #endif -#ifndef DONE_TYPEDEFS -#define DONE_TYPEDEFS -typedef struct conf_tag Conf; -typedef struct backend_tag Backend; -typedef struct terminal_tag Terminal; -#endif - #define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY" #define PUTTY_REG_PARENT "Software\\SimonTatham" #define PUTTY_REG_PARENT_CHILD "PuTTY" @@ -183,11 +195,6 @@ typedef struct terminal_tag Terminal; #define DEFAULT_CODEPAGE CP_ACP #define USES_VTLINE_HACK -typedef HDC Context; - -typedef unsigned int uint32; /* int is 32-bits on Win32 and Win64. */ -#define PUTTY_UINT32_DEFINED - #ifndef NO_GSSAPI /* * GSS-API stuff @@ -221,17 +228,47 @@ GLOBAL HINSTANCE hinst; */ void init_help(void); void shutdown_help(void); -int has_help(void); +bool has_help(void); void launch_help(HWND hwnd, const char *topic); void quit_help(HWND hwnd); /* * The terminal and logging context are notionally local to the * Windows front end, but they must be shared between window.c and - * windlg.c. Likewise the saved-sessions list. + * windlg.c. Likewise the Seat structure for the Windows GUI, and the + * Conf for the main session.. */ GLOBAL Terminal *term; -GLOBAL void *logctx; +GLOBAL LogContext *logctx; +GLOBAL Conf *conf; + +/* + * 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, char *key_fingerprint, + 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); + +/* + * The Windows GUI seat object itself, so that its methods can be + * called outside window.c. + */ +extern Seat *const win_seat; + +/* + * 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) @@ -275,7 +312,17 @@ GLOBAL void *logctx; /* * Exports from winnet.c. */ -extern int select_result(WPARAM, LPARAM); +/* 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); /* * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on @@ -303,9 +350,19 @@ DECL_WINDOWS_FUNCTION(GLOBAL, int, select, fd_set FAR *, const struct timeval FAR *)); #endif -extern int socket_writable(SOCKET skt); +/* + * Provided by each client of winnet.c, and called by winnet.c to turn + * on or off WSA*Select for a given socket. + */ +char *do_select(SOCKET skt, bool startup); -extern void socket_reselect_all(void); +/* + * 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 */ /* * Exports from winctrls.c. @@ -320,12 +377,13 @@ struct ctlpos { 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, int preserve, int save); +bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); filereq *filereq_new(void); void filereq_free(filereq *state); int message_box(LPCTSTR text, LPCTSTR caption, DWORD style, DWORD helpctxid); @@ -340,7 +398,7 @@ struct prefslist { int listid, upbid, dnbid; int srcitem; int dummyitem; - int dragging; + bool dragging; }; /* @@ -355,13 +413,17 @@ struct dlgparam { char *errtitle; /* title of error sub-messageboxes */ void *data; /* data to pass in refresh events */ union control *focused, *lastfocused; /* which ctrl has focus now/before */ - char shortcuts[128]; /* track which shortcuts in use */ - int coloursel_wanted; /* has an event handler asked for + 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, ok; } coloursel_result; /* 0-255 */ + struct { + unsigned char r, g, b; /* 0-255 */ + bool ok; + } coloursel_result; tree234 *privdata; /* stores per-control private data */ - int ended, endresult; /* has the dialog been ended? */ - int fixed_pitch_fonts; /* are we constrained to fixed fonts? */ + 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? */ }; /* @@ -374,7 +436,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, int password, char *text, +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, ...); @@ -411,7 +473,7 @@ void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, char *stext, int sid, int listid, int upbid, int dnbid); int handle_prefslist(struct prefslist *hdl, int *array, int maxmemb, - int is_dlmsg, HWND hwnd, + 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, @@ -420,9 +482,9 @@ void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid, char *btext, int bid, char *r1text, int r1id, char *r2text, int r2id); -void dlg_auto_set_fixed_pitch_flag(void *dlg); -int dlg_get_fixed_pitch_flag(void *dlg); -void dlg_set_fixed_pitch_flag(void *dlg, int flag); +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 @@ -474,10 +536,10 @@ struct winctrl *winctrl_findbyid(struct winctrls *, int); struct winctrl *winctrl_findbyindex(struct winctrls *, int); void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, struct ctlpos *cp, struct controlset *s, int *id); -int winctrl_handle_command(struct dlgparam *dp, UINT msg, - WPARAM wParam, LPARAM lParam); +bool winctrl_handle_command(struct dlgparam *dp, UINT msg, + WPARAM wParam, LPARAM lParam); void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c); -int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id); +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); @@ -486,15 +548,15 @@ void dp_cleanup(struct dlgparam *dp); /* * Exports from wincfg.c. */ -void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, - int midsession, int protocol); +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); -int do_config(void); -int do_reconfig(HWND, int); +bool do_config(void); +bool do_reconfig(HWND, int); void showeventlog(HWND); void showabout(HWND); void force_normal(HWND hwnd); @@ -504,19 +566,37 @@ void show_help(HWND hwnd); /* * Exports from winmisc.c. */ -extern OSVERSIONINFO osVersion; +GLOBAL DWORD osMajorVersion, osMinorVersion, osPlatformId; +void init_winver(void); void dll_hijacking_protection(void); -BOOL init_winver(void); HMODULE load_system32_dll(const char *libname); const char *win_strerror(int error); void restrict_process_acl(void); -GLOBAL int restricted_acl; +GLOBAL bool restricted_acl; +void escape_registry_key(const char *in, strbuf *out); +void unescape_registry_key(const char *in, strbuf *out); + +/* 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(int bEnable); +void EnableSizeTip(bool bEnable); /* * Exports from unicode.c. @@ -563,7 +643,7 @@ void agent_schedule_callback(void (*callback)(void *, void *, int), /* * Exports from winser.c. */ -extern Backend serial_backend; +extern const struct BackendVtable serial_backend; /* * Exports from winjump.c. @@ -572,7 +652,12 @@ extern Backend serial_backend; 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(); +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 @@ -597,4 +682,15 @@ int remove_from_jumplist_registry(const char *item); * 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 + #endif diff --git a/windows/winucs.c b/windows/winucs.c index 0ecd225e..7195081a 100644 --- a/windows/winucs.c +++ b/windows/winucs.c @@ -440,7 +440,7 @@ static void link_font(WCHAR * line_tbl, WCHAR * font_tbl, WCHAR attr); void init_ucs(Conf *conf, struct unicode_data *ucsdata) { int i, j; - int used_dtf = 0; + bool used_dtf = false; int vtmode; /* Decide on the Line and Font codepages */ @@ -449,13 +449,13 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) if (ucsdata->font_codepage <= 0) { ucsdata->font_codepage=0; - ucsdata->dbcs_screenfont=0; + ucsdata->dbcs_screenfont=false; } vtmode = conf_get_int(conf, CONF_vtmode); if (vtmode == VT_OEMONLY) { ucsdata->font_codepage = 437; - ucsdata->dbcs_screenfont = 0; + ucsdata->dbcs_screenfont = false; if (ucsdata->line_codepage <= 0) ucsdata->line_codepage = GetACP(); } else if (ucsdata->line_codepage <= 0) @@ -493,7 +493,7 @@ void init_ucs(Conf *conf, struct unicode_data *ucsdata) vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { /* For DBCS and POOR fonts force direct to font */ - used_dtf = 1; + used_dtf = true; for (i = 0; i < 32; i++) ucsdata->unitab_line[i] = (WCHAR) i; for (i = 32; i < 256; i++) @@ -1157,7 +1157,7 @@ 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, int *defused, + char *mbstr, int mblen, const char *defchr, struct unicode_data *ucsdata) { char *p; @@ -1180,7 +1180,6 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, int j; for (j = 0; defchr[j]; j++) *p++ = defchr[j]; - if (defused) *defused = 1; } #if 1 else @@ -1189,9 +1188,11 @@ int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, assert(p - mbstr < mblen); } return p - mbstr; - } else + } else { + int defused; return WideCharToMultiByte(codepage, flags, wcstr, wclen, - mbstr, mblen, defchr, defused); + mbstr, mblen, defchr, &defused); + } } int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, @@ -1200,7 +1201,7 @@ int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); } -int is_dbcs_leadbyte(int codepage, char byte) +bool is_dbcs_leadbyte(int codepage, char byte) { return IsDBCSLeadByteEx(codepage, byte); } diff --git a/windows/winutils.c b/windows/winutils.c index 31b98d18..5edde65b 100644 --- a/windows/winutils.c +++ b/windows/winutils.c @@ -34,17 +34,17 @@ struct filereq_tag { * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName * `state' is optional. */ -BOOL request_file(filereq *state, OPENFILENAME *of, int preserve, int save) +bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) { TCHAR cwd[MAX_PATH]; /* process CWD */ - BOOL ret; + bool ret; /* Get process CWD */ if (preserve) { DWORD r = GetCurrentDirectory(lenof(cwd), cwd); if (r == 0 || r >= lenof(cwd)) /* Didn't work, oh well. Stop trying to be clever. */ - preserve = 0; + preserve = false; } /* Open the file requester, maybe setting lpstrInitialDir */ @@ -142,12 +142,12 @@ void pgp_fingerprints(void) "one. See the manual for more information.\n" "(Note: these fingerprints have nothing to do with SSH!)\n" "\n" - "PuTTY Master Key as of 2015 (RSA, 4096-bit):\n" + "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR + " (" PGP_MASTER_KEY_DETAILS "):\n" " " PGP_MASTER_KEY_FP "\n\n" - "Original PuTTY Master Key (RSA, 1024-bit):\n" - " " PGP_RSA_MASTER_KEY_FP "\n" - "Original PuTTY Master Key (DSA, 1024-bit):\n" - " " PGP_DSA_MASTER_KEY_FP, + "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)); } @@ -321,7 +321,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, p = cmdline; q = outputline; outputargc = 0; while (*p) { - int quote; + bool quote; /* Skip whitespace searching for start of argument. */ while (*p && isspace(*p)) p++; @@ -331,7 +331,7 @@ void split_into_argv(char *cmdline, int *argc, char ***argv, outputargv[outputargc] = q; outputargstart[outputargc] = p; outputargc++; - quote = 0; + quote = false; /* Copy data into the argument until it's finished. */ while (*p) { diff --git a/windows/winx11.c b/windows/winx11.c index b8c7fa7d..e85fe4fb 100644 --- a/windows/winx11.c +++ b/windows/winx11.c @@ -16,4 +16,4 @@ void platform_get_x11_auth(struct X11Display *disp, Conf *conf) x11_get_auth_from_authfile(disp, xauthpath); } -const int platform_uses_x11_unix_by_default = FALSE; +const bool platform_uses_x11_unix_by_default = false; diff --git a/x11fwd.c b/x11fwd.c index 584116aa..b0bfa3c1 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -9,6 +9,7 @@ #include "putty.h" #include "ssh.h" +#include "sshchan.h" #include "tree234.h" #define GET_16BIT(endian, cp) \ @@ -26,23 +27,24 @@ struct XDMSeen { unsigned char clientid[6]; }; -struct X11Connection { - const struct plug_function_table *fn; - /* the above variable absolutely *must* be the first in this structure */ +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; - int verified; - int throttled, throttle_override; - int no_data_sent_to_x_client; + bool verified; + bool input_wanted; + bool no_data_sent_to_x_client; char *peer_addr; int peer_port; - struct ssh_channel *c; /* channel structure held by ssh.c */ - Socket s; -}; + SshChannel *c; /* channel structure held by SSH backend */ + Socket *s; + + Plug plug; + Channel chan; +} X11Connection; static int xdmseen_cmp(void *a, void *b) { @@ -52,24 +54,6 @@ static int xdmseen_cmp(void *a, void *b) memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid)); } -/* Do-nothing "plug" implementation, used by x11_setup_display() when it - * creates a trial connection (and then immediately closes it). - * XXX: bit out of place here, could in principle live in a platform- - * independent network.c or something */ -static void dummy_plug_log(Plug p, int type, SockAddr addr, int port, - const char *error_msg, int error_code) { } -static int dummy_plug_closing - (Plug p, const char *error_msg, int error_code, int calling_back) -{ return 1; } -static int dummy_plug_receive(Plug p, int urgent, char *data, int len) -{ return 1; } -static void dummy_plug_sent(Plug p, int bufsize) { } -static int dummy_plug_accepting(Plug p, accept_fn_t constructor, accept_ctx_t ctx) { return 1; } -static const struct plug_function_table dummy_plug = { - dummy_plug_log, dummy_plug_closing, dummy_plug_receive, - dummy_plug_sent, dummy_plug_accepting -}; - struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype) { struct X11FakeAuth *auth = snew(struct X11FakeAuth); @@ -144,7 +128,8 @@ struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype) auth->data[i]); auth->disp = NULL; - auth->share_cs = auth->share_chan = NULL; + auth->share_cs = NULL; + auth->share_chan = NULL; return auth; } @@ -190,11 +175,14 @@ int x11_authcmp(void *av, void *bv) } } -struct X11Display *x11_setup_display(const char *display, Conf *conf) +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) { @@ -221,7 +209,7 @@ struct X11Display *x11_setup_display(const char *display, Conf *conf) */ if (localcopy[0] == '/') { disp->unixsocketpath = localcopy; - disp->unixdomain = TRUE; + disp->unixdomain = true; disp->hostname = NULL; disp->displaynum = -1; disp->screennum = 0; @@ -232,9 +220,12 @@ struct X11Display *x11_setup_display(const char *display, Conf *conf) colon = host_strrchr(localcopy, ':'); if (!colon) { + *error_msg = dupprintf("display name '%s' has no ':number'" + " suffix", localcopy); + sfree(disp); sfree(localcopy); - return NULL; /* FIXME: report a specific error? */ + return NULL; } *colon++ = '\0'; @@ -267,7 +258,7 @@ struct X11Display *x11_setup_display(const char *display, Conf *conf) else if (!*hostname || !strcmp(hostname, "unix")) disp->unixdomain = platform_uses_x11_unix_by_default; else - disp->unixdomain = FALSE; + disp->unixdomain = false; if (!disp->hostname && !disp->unixdomain) disp->hostname = dupstr("localhost"); @@ -290,11 +281,14 @@ struct X11Display *x11_setup_display(const char *display, Conf *conf) 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; /* FIXME: report an error */ + return NULL; } } @@ -303,13 +297,13 @@ struct X11Display *x11_setup_display(const char *display, Conf *conf) * 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); + SockAddr *ux = platform_get_x11_unix_address(NULL, disp->displaynum); const char *err = sk_addr_error(ux); if (!err) { /* Create trial connection to see if there is a useful Unix-domain * socket */ - const struct plug_function_table *dummy = &dummy_plug; - Socket s = sk_new(sk_addr_dup(ux), 0, 0, 0, 0, 0, (Plug)&dummy); + Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, + false, false, nullplug); err = sk_socket_error(s); sk_close(s); } @@ -317,7 +311,7 @@ struct X11Display *x11_setup_display(const char *display, Conf *conf) sk_addr_free(ux); } else { sk_addr_free(disp->addr); - disp->unixdomain = TRUE; + disp->unixdomain = true; disp->addr = ux; /* Fill in the rest in a moment */ } @@ -448,16 +442,46 @@ 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_data(bs, pl.ptr, pl.len); +} +#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, *ptr, *str[4]; - int len[4]; + char *buf; + int size; + BinarySource src[1]; int family, protocol; - int ideal_match = FALSE; + 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: @@ -479,7 +503,7 @@ void x11_get_auth_from_authfile(struct X11Display *disp, * that is; so if we can't find a Unix-domain-socket entry we'll * fall back to an IP-based entry if we can find one. */ - int localhost = !disp->unixdomain && sk_address_is_local(disp->addr); + bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); authfp = fopen(authfilename, "rb"); if (!authfp) @@ -487,29 +511,41 @@ void x11_get_auth_from_authfile(struct X11Display *disp, ourhostname = get_hostname(); - /* Records in .Xauthority contain four strings of up to 64K each */ - buf = snewn(65537 * 4, char); + /* + * 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) { - int c, i, j, match = FALSE; - -#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0) - /* Expect a big-endian 2-byte number giving address family */ - GET; family = c; - GET; family = (family << 8) | c; - /* Then expect four strings, each composed of a big-endian 2-byte - * length field followed by that many bytes of data */ - ptr = buf; - for (i = 0; i < 4; i++) { - GET; len[i] = c; - GET; len[i] = (len[i] << 8) | c; - str[i] = ptr; - for (j = 0; j < len[i]; j++) { - GET; *ptr++ = c; - } - *ptr++ = '\0'; - } -#undef GET + 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 = atoi(displaynum_string); + 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 @@ -523,7 +559,7 @@ void x11_get_auth_from_authfile(struct X11Display *disp, * connect to the display. 0 means IPv4; 6 means IPv6; * 256 means Unix-domain sockets. * - * - str[0] is the network address itself. For IPv4 and + * - '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. @@ -534,24 +570,23 @@ void x11_get_auth_from_authfile(struct X11Display *disp, * authority entries for Unix-domain displays on * several machines without them clashing). * - * - str[1] is the display number. I've no idea why + * - 'displaynum' is the display number. I've no idea why * .Xauthority stores this as a string when it has a * perfectly good integer format, but there we go. * - * - str[2] is the authorisation method, encoded as its - * canonical string name (i.e. "MIT-MAGIC-COOKIE-1", - * "XDM-AUTHORIZATION-1" or something we don't - * recognise). + * - '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). * - * - str[3] is the actual authorisation data, stored in + * - 'data' is the actual authorisation data, stored in * binary form. */ - if (disp->displaynum < 0 || disp->displaynum != atoi(str[1])) + if (disp->displaynum < 0 || disp->displaynum != displaynum) continue; /* not the one */ for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (!strcmp(str[2], 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 */ @@ -562,8 +597,8 @@ void x11_get_auth_from_authfile(struct X11Display *disp, sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { char buf[4]; sk_addrcopy(disp->addr, buf); - if (len[0] == 4 && !memcmp(str[0], buf, 4)) { - match = TRUE; + if (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; @@ -575,18 +610,20 @@ void x11_get_auth_from_authfile(struct X11Display *disp, sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { char buf[16]; sk_addrcopy(disp->addr, buf); - if (len[0] == 16 && !memcmp(str[0], buf, 16)) { - match = TRUE; + 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 && !strcmp(ourhostname, str[0])) + && ourhostname && ptrlen_eq_string(addr, ourhostname)) { /* A matching Unix-domain socket is always the best * match. */ - match = ideal_match = TRUE; + match = true; + ideal_match = true; + } break; } @@ -594,20 +631,52 @@ void x11_get_auth_from_authfile(struct X11Display *disp, /* Current best guess -- may be overridden if !ideal_match */ disp->localauthproto = protocol; sfree(disp->localauthdata); /* free previous guess, if any */ - disp->localauthdata = snewn(len[3], unsigned char); - memcpy(disp->localauthdata, str[3], len[3]); - disp->localauthdatalen = len[3]; + disp->localauthdata = snewn(data.len, unsigned char); + memcpy(disp->localauthdata, data.ptr, data.len); + disp->localauthdatalen = data.len; } } - done: fclose(authfp); - smemclr(buf, 65537 * 4); + smemclr(buf, 2 * MAX_RECORD_SIZE); sfree(buf); sfree(ourhostname); } -static void x11_log(Plug p, int type, SockAddr addr, int port, +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 { + assert(false && "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, int type, SockAddr *addr, int port, const char *error_msg, int error_code) { /* We have no interface to the logging module here, so we drop these. */ @@ -616,10 +685,11 @@ static void x11_log(Plug p, int type, SockAddr addr, int port, static void x11_send_init_error(struct X11Connection *conn, const char *err_message); -static int x11_closing(Plug plug, const char *error_msg, int error_code, - int calling_back) +static void x11_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) { - struct X11Connection *xconn = (struct X11Connection *) plug; + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); if (error_msg) { /* @@ -637,7 +707,7 @@ static int x11_closing(Plug plug, const char *error_msg, int error_code, * Whether we did that or not, now we slam the connection * shut. */ - sshfwd_unclean_close(xconn->c, error_msg); + sshfwd_initiate_close(xconn->c, error_msg); } else { /* * Ordinary EOF received on socket. Send an EOF on the SSH @@ -646,26 +716,21 @@ static int x11_closing(Plug plug, const char *error_msg, int error_code, if (xconn->c) sshfwd_write_eof(xconn->c); } - - return 1; } -static int x11_receive(Plug plug, int urgent, char *data, int len) +static void x11_receive(Plug *plug, int urgent, char *data, int len) { - struct X11Connection *xconn = (struct X11Connection *) plug; - - if (sshfwd_write(xconn->c, data, len) > 0) { - xconn->throttled = 1; - xconn->no_data_sent_to_x_client = FALSE; - sk_set_frozen(xconn->s, 1); - } + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); - return 1; + xconn->no_data_sent_to_x_client = false; + sshfwd_write(xconn->c, data, len); } -static void x11_sent(Plug plug, int bufsize) +static void x11_sent(Plug *plug, int bufsize) { - struct X11Connection *xconn = (struct X11Connection *) plug; + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); sshfwd_unthrottle(xconn->c, bufsize); } @@ -688,34 +753,69 @@ int x11_get_screen_number(char *display) return atoi(display + n + 1); } +static const PlugVtable X11Connection_plugvt = { + x11_log, + x11_closing, + x11_receive, + x11_sent, + NULL +}; + +static void x11_chan_free(Channel *chan); +static int x11_send(Channel *chan, bool is_stderr, const void *vdata, int 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 struct ChannelVtable X11Connection_channelvt = { + x11_chan_free, + chan_remotely_opened_confirmation, + chan_remotely_opened_failure, + x11_send, + x11_send_eof, + x11_set_input_wanted, + x11_log_close_msg, + chan_default_want_close, + chan_no_exit_status, + chan_no_exit_signal, + chan_no_exit_signal_numeric, + chan_no_run_shell, + chan_no_run_command, + chan_no_run_subsystem, + chan_no_enable_x11_forwarding, + chan_no_enable_agent_forwarding, + chan_no_allocate_pty, + chan_no_set_env, + chan_no_send_break, + chan_no_send_signal, + chan_no_change_window_size, + chan_no_request_response, +}; + /* * Called to set up the X11Connection structure, though this does not * yet connect to an actual server. */ -struct X11Connection *x11_init(tree234 *authtree, void *c, - const char *peeraddr, int peerport) +Channel *x11_new_channel(tree234 *authtree, SshChannel *c, + const char *peeraddr, int peerport, + bool connection_sharing_possible) { - static const struct plug_function_table fn_table = { - x11_log, - x11_closing, - x11_receive, - x11_sent, - NULL - }; - struct X11Connection *xconn; /* * Open socket. */ xconn = snew(struct X11Connection); - xconn->fn = &fn_table; + 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 = 0; + xconn->verified = false; xconn->data_read = 0; - xconn->throttled = xconn->throttle_override = 0; - xconn->no_data_sent_to_x_client = TRUE; + xconn->input_wanted = true; + xconn->no_data_sent_to_x_client = true; xconn->c = c; /* @@ -735,13 +835,13 @@ struct X11Connection *x11_init(tree234 *authtree, void *c, xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL; xconn->peer_port = peerport; - return xconn; + return &xconn->chan; } -void x11_close(struct X11Connection *xconn) +static void x11_chan_free(Channel *chan) { - if (!xconn) - return; + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); if (xconn->auth_protocol) { sfree(xconn->auth_protocol); @@ -755,24 +855,14 @@ void x11_close(struct X11Connection *xconn) sfree(xconn); } -void x11_unthrottle(struct X11Connection *xconn) +static void x11_set_input_wanted(Channel *chan, bool wanted) { - if (!xconn) - return; - - xconn->throttled = 0; - if (xconn->s) - sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); -} - -void x11_override_throttle(struct X11Connection *xconn, int enable) -{ - if (!xconn) - return; + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); - xconn->throttle_override = enable; + xconn->input_wanted = wanted; if (xconn->s) - sk_set_frozen(xconn->s, xconn->throttled || xconn->throttle_override); + sk_set_frozen(xconn->s, !xconn->input_wanted); } static void x11_send_init_error(struct X11Connection *xconn, @@ -793,14 +883,14 @@ static void x11_send_init_error(struct X11Connection *xconn, PUT_16BIT(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ memset(reply + 8, 0, msgsize); memcpy(reply + 8, full_message, msglen); - sshfwd_write(xconn->c, (char *)reply, 8 + msgsize); + sshfwd_write(xconn->c, reply, 8 + msgsize); sshfwd_write_eof(xconn->c); - xconn->no_data_sent_to_x_client = FALSE; + xconn->no_data_sent_to_x_client = false; sfree(reply); sfree(full_message); } -static int x11_parse_ip(const char *addr_string, unsigned long *ip) +static bool x11_parse_ip(const char *addr_string, unsigned long *ip) { /* @@ -811,19 +901,20 @@ static int x11_parse_ip(const char *addr_string, unsigned long *ip) 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; + return true; } else { - return FALSE; + return false; } } /* * Called to send data down the raw connection. */ -int x11_send(struct X11Connection *xconn, char *data, int len) +static int x11_send(Channel *chan, bool is_stderr, const void *vdata, int len) { - if (!xconn) - return 0; + assert(chan->vt == &X11Connection_channelvt); + X11Connection *xconn = container_of(chan, X11Connection, chan); + const char *data = (const char *)vdata; /* * Read the first packet. @@ -901,7 +992,8 @@ int x11_send(struct X11Connection *xconn, char *data, int len) /* * 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. + * 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, @@ -916,11 +1008,12 @@ int x11_send(struct X11Connection *xconn, char *data, int len) * Now we know we're going to accept the connection, and what * X display to connect to. Actually connect to it. */ - sshfwd_x11_is_local(xconn->c); + xconn->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, - 0, 1, 0, 0, (Plug) xconn, + 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" @@ -961,7 +1054,7 @@ int x11_send(struct X11Connection *xconn, char *data, int len) /* * Now we're done. */ - xconn->verified = 1; + xconn->verified = true; } /* @@ -971,8 +1064,11 @@ int x11_send(struct X11Connection *xconn, char *data, int len) return sk_write(xconn->s, data, len); } -void x11_send_eof(struct X11Connection *xconn) +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 { @@ -987,34 +1083,39 @@ void x11_send_eof(struct X11Connection *xconn) } } +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(const char *protoname) +int x11_identify_auth_proto(ptrlen protoname) { int protocol; for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (!strcmp(protoname, x11_authnames[protocol])) + if (ptrlen_eq_string(protoname, x11_authnames[protocol])) return protocol; return -1; } -void *x11_dehexify(const char *hex, int *outlen) +void *x11_dehexify(ptrlen hexpl, int *outlen) { int len, i; unsigned char *ret; - len = strlen(hex) / 2; + len = hexpl.len / 2; ret = snewn(len, unsigned char); for (i = 0; i < len; i++) { char bytestr[3]; unsigned val = 0; - bytestr[0] = hex[2*i]; - bytestr[1] = hex[2*i+1]; + bytestr[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; @@ -1063,8 +1164,7 @@ void *x11_make_greeting(int endian, int protomajor, int protominor, t = time(NULL); PUT_32BIT_MSB_FIRST(realauthdata+14, t); - des_encrypt_xdmauth((const unsigned char *)auth_data + 9, - realauthdata, authdatalen); + des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen); } else { authdata = realauthdata; authdatalen = 0;